diff --git a/.github/instructions/filament.instructions.md b/.github/instructions/filament.instructions.md index 6e7418bf..ea6c1dc3 100644 --- a/.github/instructions/filament.instructions.md +++ b/.github/instructions/filament.instructions.md @@ -18,7 +18,7 @@ Do NOT flag `Filament\Schemas\Components\Component` as incorrect. - Custom field types live in `src/FieldTypeSystem/Definitions/` - Validation capabilities live in `src/Validation/Capabilities/` -- Each capability implements `Relaticle\CustomFields\Contracts\ValidationCapability` +- Each capability implements `Relaticle\CustomFields\Contracts\ValidationCapabilityInterface` - `DateConstraintValue` is a Spatie Laravel Data class -- use `::from()` for hydration, not manual construction # Data Patterns diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 08a0204f..ddf97191 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -4,7 +4,7 @@ name: Docs # caught on the pull request instead of after the merge, when only the deploy runs. on: pull_request: - branches: [3.x] + branches: [3.x, 4.x] permissions: contents: read diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2ea0fc7f..996ca0b4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,9 +2,9 @@ name: Tests on: push: - branches: [3.x] + branches: [3.x, 4.x] pull_request: - branches: [3.x] + branches: [3.x, 4.x] workflow_call: permissions: @@ -41,18 +41,57 @@ jobs: tests: runs-on: ubuntu-latest strategy: - fail-fast: true + fail-fast: false matrix: php: [8.4] laravel: [12.*, 13.*] stability: [prefer-stable] + db: [sqlite, pgsql, mysql] + flavor: [polished] include: - laravel: 12.* testbench: 10.* - laravel: 13.* testbench: 11.* + # The forked UI surfaces render twice, so one leg reads them in the other flavor. + # A flavor no base combination carries adds a leg rather than converting one, and an + # earlier include never reaches a combination a later one creates, so it spells out + # every key it needs. + - php: 8.4 + laravel: 13.* + stability: prefer-stable + db: sqlite + flavor: native + testbench: 11.* - name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} + name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.db }} - ${{ matrix.flavor }} + + services: + postgres: + image: postgres:17 + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: password + POSTGRES_DB: custom_fields_test + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + mysql: + image: mysql:8.4 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: custom_fields_test + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=5 steps: - name: Checkout code @@ -62,7 +101,7 @@ jobs: uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, pdo_pgsql, pgsql, pdo_mysql, mysqli, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo coverage: none - name: Install dependencies @@ -80,6 +119,14 @@ jobs: run: vendor/bin/rector --dry-run --no-progress-bar - name: Run Pest + env: + DB_CONNECTION: ${{ matrix.db }} + DB_HOST: 127.0.0.1 + DB_PORT: ${{ matrix.db == 'pgsql' && '5432' || '3306' }} + DB_DATABASE: custom_fields_test + DB_USERNAME: root + DB_PASSWORD: ${{ matrix.db == 'pgsql' && 'password' || '' }} + CUSTOM_FIELDS_UI_FLAVOR: ${{ matrix.flavor }} run: vendor/bin/pest --ci gate: diff --git a/README.md b/README.md index 3b811d78..ddd337a7 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ A powerful Laravel/Filament plugin for adding dynamic custom fields to any Eloqu ## Features - **20+ Field Types** - Text, date, select, file upload, rich editor, and more +- **Record Links** - Link records to records, one-way or paired, stored in an edge ledger +- **Workflow States** - Status fields whose options carry a machine-readable category - **Conditional Visibility** - Show/hide fields based on other field values - **Multi-tenancy** - Complete tenant isolation and context management - **Filament Integration** - Forms, tables, infolists, and admin interface diff --git a/bin/custom-fields-upgrade b/bin/custom-fields-upgrade deleted file mode 100755 index 23a8e3a1..00000000 --- a/bin/custom-fields-upgrade +++ /dev/null @@ -1,774 +0,0 @@ -#!/usr/bin/env php - 10) { // Only show progress for larger operations - $percentage = round(($current / $total) * 100); - $bar = str_repeat('ā–ˆ', intval($percentage / 5)); - $spaces = str_repeat(' ', 20 - intval($percentage / 5)); - echo "\ršŸ”„ Progress: [$bar$spaces] {$percentage}% ($current/$total) $message"; - if ($current == $total) { - echo "\n"; - } - } -} - -// Import pattern upgrades -$importPatterns = [ - // Form component imports - [ - 'pattern' => '/use\s+Relaticle\\\\CustomFields\\\\Filament\\\\Forms\\\\Components\\\\CustomFieldsComponent;/', - 'replacement' => 'use Relaticle\CustomFields\Facades\CustomFields;', - 'description' => 'Form component import', - ], - // Infolist component imports - [ - 'pattern' => '/use\s+Relaticle\\\\CustomFields\\\\Filament\\\\Infolists\\\\CustomFieldsInfolists;/', - 'replacement' => 'use Relaticle\CustomFields\Facades\CustomFields;', - 'description' => 'Infolist component import', - ], - // Import/Export component imports - [ - 'pattern' => '/use\s+Relaticle\\\\CustomFields\\\\Filament\\\\Imports\\\\CustomFieldsImporter;/', - 'replacement' => 'use Relaticle\CustomFields\Facades\CustomFields;', - 'description' => 'Import component import', - ], - [ - 'pattern' => '/use\s+Relaticle\\\\CustomFields\\\\Filament\\\\Exports\\\\CustomFieldsExporter;/', - 'replacement' => 'use Relaticle\CustomFields\Facades\CustomFields;', - 'description' => 'Export component import', - ], - // Trait namespace update - [ - 'pattern' => '/use\s+Relaticle\\\\CustomFields\\\\Filament\\\\Tables\\\\Concerns\\\\InteractsWithCustomFields;/', - 'replacement' => 'use Relaticle\CustomFields\Concerns\InteractsWithCustomFields;', - 'description' => 'Table trait namespace', - ], -]; - -// Complex component usage patterns (handles multiline and chaining) -$complexPatterns = [ - // Form component with potential chaining - handles multiline - [ - 'pattern' => '/CustomFieldsComponent::make\(\)(?:\s*->[a-zA-Z_][a-zA-Z0-9_]*\([^)]*\))*\s*(?:,|\]|\)|;)/', - 'replacement' => function($matches) { - $original = $matches[0]; - $endChar = substr(trim($original), -1); - - // Extract any chained methods after make() - preg_match('/CustomFieldsComponent::make\(\)(.*?)(?:,|\]|\)|;)/', $original, $chainMatches); - $chainedMethods = isset($chainMatches[1]) ? trim($chainMatches[1]) : ''; - - return 'CustomFields::form() - ->forModel($form->getRecord()) - ->build()' . $chainedMethods . $endChar; - }, - 'description' => 'Form component usage (with chaining)', - 'requiresImport' => 'Relaticle\CustomFields\Facades\CustomFields', - ], - - // Infolist component with chaining - handles multiline - [ - 'pattern' => '/CustomFieldsInfolists::make\(\)(?:\s*->[a-zA-Z_][a-zA-Z0-9_]*\([^)]*\))*\s*(?:,|\]|\)|;)/', - 'replacement' => function($matches) { - $original = $matches[0]; - $endChar = substr(trim($original), -1); - - // Extract chained methods - preg_match('/CustomFieldsInfolists::make\(\)(.*?)(?:,|\]|\)|;)/', $original, $chainMatches); - $chainedMethods = isset($chainMatches[1]) ? trim($chainMatches[1]) : ''; - - return 'CustomFields::infolist() - ->forModel($infolist->getRecord()) - ->build()' . $chainedMethods . $endChar; - }, - 'description' => 'Infolist component usage (with chaining)', - 'requiresImport' => 'Relaticle\CustomFields\Facades\CustomFields', - ], -]; - -// Import/Export usage patterns -$importExportPatterns = [ - // CustomFieldsImporter usage in getColumns - [ - 'pattern' => '/\.\.\.app\(CustomFieldsImporter::class\)->getColumns\(([^)]+)\)/', - 'replacement' => '...CustomFields::importer() - ->forModel($1) - ->columns()', - 'description' => 'Importer getColumns usage', - 'requiresImport' => 'Relaticle\CustomFields\Facades\CustomFields', - ], - - // CustomFieldsImporter usage in getColumnsByFieldCodes (multiline) - [ - 'pattern' => '/\.\.\.app\(CustomFieldsImporter::class\)->getColumnsByFieldCodes\(\s*([^,\s]+),\s*(\[[^\]]+\])\s*\)/', - 'replacement' => '...CustomFields::importer() - ->forModel($1) - ->only($2) - ->columns()', - 'description' => 'Importer getColumnsByFieldCodes usage', - 'requiresImport' => 'Relaticle\CustomFields\Facades\CustomFields', - ], - - // CustomFieldsImporter filterCustomFieldsFromData - [ - 'pattern' => '/app\(CustomFieldsImporter::class\)->filterCustomFieldsFromData\(([^)]+)\)/', - 'replacement' => 'CustomFields::importer()->filterCustomFieldsFromData($1)', - 'description' => 'Importer filterCustomFieldsFromData usage', - 'requiresImport' => 'Relaticle\CustomFields\Facades\CustomFields', - ], - - // CustomFieldsImporter saveCustomFieldValues - [ - 'pattern' => '/app\(CustomFieldsImporter::class\)->saveCustomFieldValues\(([^;]+)\);/', - 'replacement' => function($matches) { - $params = trim($matches[1]); - // Parse parameters - typically record, data, tenant - $paramArray = array_map('trim', explode(',', $params)); - - if (count($paramArray) >= 2) { - return 'CustomFields::importer() - ->forModel(' . $paramArray[0] . ') - ->saveCustomFieldValues(' . implode(', ', $paramArray) . ');'; - } - return $matches[0]; // Fallback if parsing fails - }, - 'description' => 'Importer saveCustomFieldValues usage', - 'requiresImport' => 'Relaticle\CustomFields\Facades\CustomFields', - ], - - // CustomFieldsExporter getColumns usage - [ - 'pattern' => '/\.\.\.CustomFieldsExporter::getColumns\(([^)]+)\)/', - 'replacement' => '...CustomFields::exporter() - ->forModel($1) - ->columns()', - 'description' => 'Exporter getColumns usage', - 'requiresImport' => 'Relaticle\CustomFields\Facades\CustomFields', - ], -]; - -// Find all PHP files -$files = []; -$iterator = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($scanPath, RecursiveDirectoryIterator::SKIP_DOTS) -); - -foreach ($iterator as $file) { - if ($file->isFile() && $file->getExtension() === 'php') { - $files[] = $file->getPathname(); - } -} - -echo 'Found ' . count($files) . " PHP files to scan\n\n"; - -// Upgrade configuration file first -$configUpgraded = upgradeConfigurationFile($basePath, $dryRun, $createBackup, $verbose); - -$filesUpdated = []; -$errors = []; -$backupFiles = []; -$processedFiles = 0; - -/** - * Enhanced pattern processor that handles closures and complex patterns - */ -function processPatterns($content, $patterns, $patternType = 'simple') { - $updates = []; - $needsCustomFieldsImport = false; - - foreach ($patterns as $pattern) { - if (preg_match($pattern['pattern'], $content)) { - if (is_callable($pattern['replacement'])) { - // Handle closure-based replacements - $content = preg_replace_callback($pattern['pattern'], $pattern['replacement'], $content); - } else { - // Handle simple string replacements - $content = preg_replace($pattern['pattern'], $pattern['replacement'], $content); - } - - $updates[] = $pattern['description']; - - // Track if we need to add the CustomFields import - if (isset($pattern['requiresImport'])) { - $needsCustomFieldsImport = true; - } - } - } - - return [$content, $updates, $needsCustomFieldsImport]; -} - -/** - * Clean up imports - remove old ones and add new consolidated import - */ -function cleanupImports($content) { - // Remove all old Custom Fields imports first - $oldImports = [ - 'Relaticle\CustomFields\Filament\Forms\Components\CustomFieldsComponent', - 'Relaticle\CustomFields\Filament\Infolists\CustomFieldsInfolists', - 'Relaticle\CustomFields\Filament\Imports\CustomFieldsImporter', - 'Relaticle\CustomFields\Filament\Exports\CustomFieldsExporter', - 'Relaticle\CustomFields\Facades\CustomFields', // Also remove any existing facade imports - ]; - - foreach ($oldImports as $oldImport) { - $content = preg_replace('/^use\s+' . preg_quote($oldImport, '/') . ';\s*\n/m', '', $content); - } - - // Remove any duplicate/orphaned use statements - $content = preg_replace('/^use\s*;\s*\n/m', '', $content); - - // Add single CustomFields facade import - $newImport = 'Relaticle\CustomFields\Facades\CustomFields'; - - // Find the last use statement position - if (preg_match('/^((?:use\s+[^;]+;\s*\n)*)/m', $content, $matches)) { - // Insert after existing use statements - $useSection = $matches[1]; - $content = str_replace( - $useSection, - $useSection . "use {$newImport};\n", - $content - ); - } else { - // No use statements exist, add after namespace or at the beginning - if (preg_match('/(namespace\s+[^;]+;)/', $content)) { - $content = preg_replace( - '/(namespace\s+[^;]+;)/', - "$1\n\nuse {$newImport};", - $content, - 1 - ); - } else { - // Add at the beginning after opening PHP tag - $content = preg_replace( - '/^(<\?php\s*\n)/m', - "$1\nuse {$newImport};\n", - $content, - 1 - ); - } - } - - return $content; -} - -/** - * Check if file needs CustomFields import based on transformations made - */ -function needsCustomFieldsFacade($allUpdates) { - $requiresImportPatterns = [ - 'Form component usage', - 'Infolist component usage', - 'Importer', - 'Exporter' - ]; - - foreach ($allUpdates as $update) { - foreach ($requiresImportPatterns as $pattern) { - if (strpos($update, $pattern) !== false) { - return true; - } - } - } - - return false; -} - -/** - * Process multiline patterns that span across lines - */ -function processMultilinePatterns($content) { - $updates = []; - $needsImport = false; - - // Handle multiline CustomFieldsComponent patterns - $multilineFormPattern = '/CustomFieldsComponent::make\(\)(\s*(?:\/\/[^\n]*\n)?(?:\s*->[a-zA-Z_][a-zA-Z0-9_]*\([^)]*\)(?:\s*(?:\/\/[^\n]*)?)*)*\s*)(?=,|\]|\)|;)/s'; - if (preg_match($multilineFormPattern, $content)) { - $content = preg_replace_callback($multilineFormPattern, function($matches) { - $chainedMethods = isset($matches[1]) ? $matches[1] : ''; - return 'CustomFields::form() - ->forModel($form->getRecord()) - ->build()' . $chainedMethods; - }, $content); - $updates[] = 'Form component usage (multiline)'; - $needsImport = true; - } - - // Handle multiline CustomFieldsInfolists patterns - $multilineInfolistPattern = '/CustomFieldsInfolists::make\(\)(\s*(?:\/\/[^\n]*\n)?(?:\s*->[a-zA-Z_][a-zA-Z0-9_]*\([^)]*\)(?:\s*(?:\/\/[^\n]*)?)*)*\s*)(?=,|\]|\)|;)/s'; - if (preg_match($multilineInfolistPattern, $content)) { - $content = preg_replace_callback($multilineInfolistPattern, function($matches) { - $chainedMethods = isset($matches[1]) ? $matches[1] : ''; - return 'CustomFields::infolist() - ->forModel($infolist->getRecord()) - ->build()' . $chainedMethods; - }, $content); - $updates[] = 'Infolist component usage (multiline)'; - $needsImport = true; - } - - // Handle multiline getColumnsByFieldCodes patterns - $multilineFieldCodesPattern = '/\.\.\.app\(CustomFieldsImporter::class\)->getColumnsByFieldCodes\(\s*([^,\n]+),\s*(\[[^\]]+\])\s*\)/s'; - if (preg_match($multilineFieldCodesPattern, $content)) { - $content = preg_replace($multilineFieldCodesPattern, '...CustomFields::importer() - ->forModel($1) - ->only($2) - ->columns()', $content); - $updates[] = 'Importer getColumnsByFieldCodes usage (multiline)'; - $needsImport = true; - } - - return [$content, $updates, $needsImport]; -} - -/** - * Upgrade configuration file from v1 to v2 format - */ -function upgradeConfigurationFile($basePath, $dryRun = false, $createBackup = false, $verbose = false) { - // Config should be in the project root, not relative to scan path - $configPath = $basePath . '/config/custom-fields.php'; - - if (!file_exists($configPath)) { - if ($verbose) { - echo "ā„¹ļø No config file found at: $configPath\n"; - } - return false; - } - - $content = file_get_contents($configPath); - $originalContent = $content; - - // Check if it's already v2 format - if (strpos($content, 'EntityConfigurator::') !== false || - strpos($content, 'FeatureConfigurator::') !== false || - strpos($content, 'FieldTypeConfigurator::') !== false) { - if ($verbose) { - echo "āœ… Config file already appears to be v2 format\n"; - } - return false; - } - - echo "šŸ”§ Upgrading configuration file...\n"; - - // Create v2 configuration template - $v2Config = ' EntityConfigurator::configure() - ->discover(app_path(\'Models\')) - ->cache(false), - - /* - |-------------------------------------------------------------------------- - | Advanced Field Type Configuration - |-------------------------------------------------------------------------- - */ - \'field_type_configuration\' => FieldTypeConfigurator::configure() - ->enabled([]) // Empty = all enabled - ->disabled([]) // Add field types to disable - ->discover(true) - ->cache(enabled: false, ttl: 3400), - - /* - |-------------------------------------------------------------------------- - | Features Configuration - |-------------------------------------------------------------------------- - */ - \'features\' => FeatureConfigurator::configure() - ->enable( - CustomFieldsFeature::FIELD_ENCRYPTION, - CustomFieldsFeature::UI_TABLE_COLUMNS, - CustomFieldsFeature::UI_TOGGLEABLE_COLUMNS, - CustomFieldsFeature::UI_TABLE_FILTERS, - CustomFieldsFeature::SYSTEM_MANAGEMENT_INTERFACE - ) - ->disable( - CustomFieldsFeature::SYSTEM_MULTI_TENANCY - ), - - /* - |-------------------------------------------------------------------------- - | Management Interface Configuration - |-------------------------------------------------------------------------- - */ - \'management\' => [ - \'slug\' => \'custom-fields\', - \'navigation_sort\' => -1, - \'navigation_group\' => true, - \'cluster\' => null, - ], - - /* - |-------------------------------------------------------------------------- - | Database Configuration - |-------------------------------------------------------------------------- - */ - \'database\' => [ - \'migrations_path\' => database_path(\'custom-fields\'), - \'table_names\' => [ - \'custom_field_sections\' => \'custom_field_sections\', - \'custom_fields\' => \'custom_fields\', - \'custom_field_values\' => \'custom_field_values\', - \'custom_field_options\' => \'custom_field_options\', - ], - \'column_names\' => [ - \'tenant_foreign_key\' => \'tenant_id\', - ], - ], -]; -'; - - if ($dryRun) { - echo "šŸ” Would upgrade config file: config/custom-fields.php\n"; - if ($verbose) { - echo " - Convert v1 array format to v2 configurators\n"; - echo " - Enable features based on v1 settings\n"; - echo " - Add new v2 sections (entity_configuration, management, database)\n"; - } - return true; - } else { - // Create backup if requested - if ($createBackup) { - $backupPath = createBackupFile($configPath); - if ($backupPath) { - echo "šŸ’¾ Config backup created: " . str_replace($basePath . '/', '', $backupPath) . "\n"; - } - } - - // Write v2 config - if (file_put_contents($configPath, $v2Config)) { - echo "āœ… Config file upgraded to v2 format\n"; - if ($verbose) { - echo " - Converted to FeatureConfigurator, FieldTypeConfigurator, EntityConfigurator\n"; - echo " - Added management and database sections\n"; - echo " - Preserved encryption and table features\n"; - } - return true; - } else { - echo "āŒ Failed to write config file\n"; - return false; - } - } -} - -// Process each file -foreach ($files as $filePath) { - $processedFiles++; - $content = file_get_contents($filePath); - $originalContent = $content; - $allUpdates = []; - $needsCustomFieldsImport = false; - - // Show progress for large operations - showProgress($processedFiles, count($files), basename($filePath)); - - // Process import pattern updates - [$content, $importUpdates, $needsImport1] = processPatterns($content, $importPatterns, 'imports'); - $allUpdates = array_merge($allUpdates, $importUpdates); - $needsCustomFieldsImport = $needsCustomFieldsImport || $needsImport1; - - // Process complex component patterns - [$content, $complexUpdates, $needsImport2] = processPatterns($content, $complexPatterns, 'complex'); - $allUpdates = array_merge($allUpdates, $complexUpdates); - $needsCustomFieldsImport = $needsCustomFieldsImport || $needsImport2; - - // Process import/export patterns - [$content, $importExportUpdates, $needsImport3] = processPatterns($content, $importExportPatterns, 'import-export'); - $allUpdates = array_merge($allUpdates, $importExportUpdates); - $needsCustomFieldsImport = $needsCustomFieldsImport || $needsImport3; - - // Process multiline patterns - [$content, $multilineUpdates, $needsImport4] = processMultilinePatterns($content); - $allUpdates = array_merge($allUpdates, $multilineUpdates); - $needsCustomFieldsImport = $needsCustomFieldsImport || $needsImport4; - - // Clean up imports if any Custom Fields transformations were made - if ($needsCustomFieldsImport || needsCustomFieldsFacade($allUpdates)) { - $content = cleanupImports($content); - } - - // Save if changed - if ($content !== $originalContent) { - $relativePath = str_replace($basePath . '/', '', $filePath); - - if ($dryRun) { - // Dry run mode - just show what would be changed - echo "šŸ” Would update: $relativePath\n"; - foreach ($allUpdates as $update) { - echo " - $update\n"; - } - if ($verbose) { - echo " šŸ“„ Preview of changes:\n"; - $diff = getDiffPreview($originalContent, $content); - echo $diff . "\n"; - } - $filesUpdated[$relativePath] = $allUpdates; - } else { - // Create backup if requested - if ($createBackup) { - $backupPath = createBackupFile($filePath); - if ($backupPath) { - $backupFiles[] = $backupPath; - if ($verbose) { - echo "šŸ’¾ Backup created: $backupPath\n"; - } - } else { - echo "āš ļø Warning: Could not create backup for $relativePath\n"; - } - } - - // Write the actual changes - if (file_put_contents($filePath, $content)) { - $filesUpdated[$relativePath] = $allUpdates; - echo "āœ… Updated: $relativePath\n"; - if ($verbose || count($allUpdates) <= 5) { - foreach ($allUpdates as $update) { - echo " - $update\n"; - } - } else { - echo " - " . count($allUpdates) . " changes made (use --verbose to see details)\n"; - } - } else { - $errors[] = $relativePath; - echo "āŒ Failed to update: $relativePath\n"; - } - } - } else if ($verbose) { - echo "⚪ No changes needed: " . str_replace($basePath . '/', '', $filePath) . "\n"; - } -} - -/** - * Generate a simple diff preview - */ -function getDiffPreview($original, $new) { - $originalLines = explode("\n", $original); - $newLines = explode("\n", $new); - $diff = ""; - $maxLines = 5; // Show max 5 lines of diff - $lineCount = 0; - - for ($i = 0; $i < min(count($originalLines), count($newLines)) && $lineCount < $maxLines; $i++) { - if ($originalLines[$i] !== $newLines[$i]) { - $diff .= " - " . trim($originalLines[$i]) . "\n"; - $diff .= " + " . trim($newLines[$i]) . "\n"; - $lineCount++; - } - } - - if ($lineCount == $maxLines) { - $diff .= " ... (showing first $maxLines changes)\n"; - } - - return $diff; -} - -// Show summary -echo "\n========================================\n"; -if ($dryRun) { - echo "šŸ” Dry Run Complete - No Files Modified\n"; -} else { - echo "šŸŽ‰ Upgrade Process Complete!\n"; -} -echo "========================================\n\n"; - -if (count($filesUpdated) > 0 || $configUpgraded) { - if ($dryRun) { - echo "šŸ” Changes that would be made:\n"; - if ($configUpgraded) echo " • Configuration file (config/custom-fields.php)\n"; - echo " • " . count($filesUpdated) . " PHP files\n\n"; - echo "Files that would be changed:\n"; - } else { - echo "āœ… Upgrade completed:\n"; - if ($configUpgraded) echo " • Configuration file upgraded\n"; - echo " • " . count($filesUpdated) . " PHP files updated\n\n"; - echo "Updated files:\n"; - } - - foreach ($filesUpdated as $file => $updates) { - echo " • $file"; - if ($verbose) { - echo " (" . count($updates) . " changes)"; - } - echo "\n"; - } -} else { - if ($configUpgraded) { - echo "āœ… Configuration file upgraded. No PHP file changes needed!\n"; - } else { - echo "āœ… No v1 components found. Your codebase appears to be already using v2!\n"; - } -} - -// Show backup information -if (!$dryRun && $createBackup && count($backupFiles) > 0) { - echo "\nšŸ’¾ Backup files created:\n"; - foreach ($backupFiles as $backupFile) { - echo " • " . str_replace($basePath . '/', '', $backupFile) . "\n"; - } - echo "\nTo restore backups if needed:\n"; - echo " find . -name '*.v1-backup-*' -exec sh -c 'mv \"\$1\" \"\${1%.v1-backup-*}\"' _ {} \\;\n"; -} - -if (count($errors) > 0) { - echo "\nāš ļø Errors encountered:\n"; - foreach ($errors as $file) { - echo " • $file\n"; - } - echo "\nPlease review and fix these errors manually.\n"; -} - -// Next steps advice -echo "\n✨ Next Steps:\n"; - -if ($dryRun) { - echo "šŸ” You ran in dry-run mode. To apply these changes:\n"; - echo "1. Run the upgrade script without --dry-run:\n"; - echo " vendor/bin/custom-fields-upgrade"; - if (isset($options['path'])) { - echo " --path=" . $options['path']; - } - if ($createBackup) { - echo " --backup"; - } - echo "\n\n"; - echo "2. Consider using --backup flag to create backups before changes\n"; -} else if (count($filesUpdated) > 0) { - echo "1. Clear Laravel caches:\n"; - echo " php artisan cache:clear\n"; - echo " php artisan config:clear\n"; - echo " php artisan view:clear\n"; - echo " php artisan filament:cache-components\n\n"; - - echo "2. Review your configuration (config/custom-fields.php):\n"; - echo " - Field types are now managed via 'field_type_configuration' using FieldTypeConfigurator\n"; - echo " - Custom field types extend BaseFieldType and use FieldSchema (see docs)\n\n"; - - echo "3. Review the updated files to ensure everything looks correct\n"; - echo "4. Run your test suite to verify functionality\n"; - echo "5. Check your Filament resources in the browser\n\n"; - - if (count($backupFiles) > 0) { - echo "6. Remove backup files after confirming everything works:\n"; - echo " find . -name '*.v1-backup-*' -delete\n\n"; - } -} else { - echo "šŸŽ‰ No changes were needed - you're already using v2 syntax!\n"; - echo "You can still review your configuration and run cache clear commands if needed.\n\n"; -} - -echo "šŸ“š For more information:\n"; -echo "- Upgrade guide: https://custom-fields.dev/docs/v2/upgrade\n"; -echo "- V2 documentation: https://custom-fields.dev/docs/v2/introduction\n"; -echo "- Configuration: https://custom-fields.dev/docs/v2/essentials/configuration\n\n"; - -// Statistics -if ($verbose) { - echo "šŸ“Š Upgrade Statistics:\n"; - echo "- Files scanned: " . count($files) . "\n"; - echo "- Files modified: " . count($filesUpdated) . "\n"; - echo "- Errors encountered: " . count($errors) . "\n"; - if (!$dryRun && $createBackup) { - echo "- Backup files created: " . count($backupFiles) . "\n"; - } - echo "\n"; -} - -exit(count($errors) > 0 ? 1 : 0); diff --git a/composer.json b/composer.json index 49768b71..b35bb133 100644 --- a/composer.json +++ b/composer.json @@ -90,9 +90,6 @@ ], "test-coverage": "vendor/bin/pest --coverage" }, - "bin": [ - "bin/custom-fields-upgrade" - ], "config": { "sort-packages": true, "allow-plugins": { diff --git a/composer.lock b/composer.lock index 0cdb7648..87e29dcc 100644 --- a/composer.lock +++ b/composer.lock @@ -6,6 +6,71 @@ ], "content-hash": "7765ee9f06f71fb6a34ca791ef328047", "packages": [ + { + "name": "anourvalar/eloquent-serialize", + "version": "1.3.11", + "source": { + "type": "git", + "url": "https://github.com/AnourValar/eloquent-serialize.git", + "reference": "abd890c4d1ad8e90dd454d01283fbf342f80f1e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/AnourValar/eloquent-serialize/zipball/abd890c4d1ad8e90dd454d01283fbf342f80f1e5", + "reference": "abd890c4d1ad8e90dd454d01283fbf342f80f1e5", + "shasum": "" + }, + "require": { + "laravel/framework": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "php": "^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.26", + "laravel/legacy-factories": "^1.1", + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^9.5|^10.5|^11.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "EloquentSerialize": "AnourValar\\EloquentSerialize\\Facades\\EloquentSerializeFacade" + } + } + }, + "autoload": { + "psr-4": { + "AnourValar\\EloquentSerialize\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Laravel Query Builder (Eloquent) serialization", + "homepage": "https://github.com/AnourValar/eloquent-serialize", + "keywords": [ + "anourvalar", + "builder", + "copy", + "eloquent", + "job", + "laravel", + "query", + "querybuilder", + "queue", + "serializable", + "serialization", + "serialize" + ], + "support": { + "issues": "https://github.com/AnourValar/eloquent-serialize/issues", + "source": "https://github.com/AnourValar/eloquent-serialize/tree/1.3.11" + }, + "time": "2026-08-07T06:14:19+00:00" + }, { "name": "blade-ui-kit/blade-heroicons", "version": "2.7.0", @@ -77,16 +142,16 @@ }, { "name": "blade-ui-kit/blade-icons", - "version": "1.10.0", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/driesvints/blade-icons.git", - "reference": "74189a80bbaa4966aebaee54fec3a3c2ef0a5f3a" + "reference": "6e072d021ea6249986c330b93293c33d0c4f0e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/driesvints/blade-icons/zipball/74189a80bbaa4966aebaee54fec3a3c2ef0a5f3a", - "reference": "74189a80bbaa4966aebaee54fec3a3c2ef0a5f3a", + "url": "https://api.github.com/repos/driesvints/blade-icons/zipball/6e072d021ea6249986c330b93293c33d0c4f0e34", + "reference": "6e072d021ea6249986c330b93293c33d0c4f0e34", "shasum": "" }, "require": { @@ -154,27 +219,26 @@ "type": "paypal" } ], - "time": "2026-04-23T19:03:45+00:00" + "time": "2026-06-30T09:44:12+00:00" }, { "name": "brick/math", - "version": "0.14.8", + "version": "0.18.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629" + "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629", - "reference": "63422359a44b7f06cae63c3b429b59e8efcc0629", + "url": "https://api.github.com/repos/brick/math/zipball/82944324d1c1bdb2c2618e89978d4e2ad78d69ad", + "reference": "82944324d1c1bdb2c2618e89978d4e2ad78d69ad", "shasum": "" }, "require": { "php": "^8.2" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.2", "phpstan/phpstan": "2.1.22", "phpunit/phpunit": "^11.5" }, @@ -206,7 +270,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.14.8" + "source": "https://github.com/brick/math/tree/0.18.0" }, "funding": [ { @@ -214,7 +278,7 @@ "type": "github" } ], - "time": "2026-02-10T14:33:43+00:00" + "time": "2026-06-14T18:21:03+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -500,16 +564,16 @@ }, { "name": "danharrin/livewire-rate-limiting", - "version": "v2.2.0", + "version": "v2.2.1", "source": { "type": "git", "url": "https://github.com/danharrin/livewire-rate-limiting.git", - "reference": "c03e649220089f6e5a52d422e24e3f98c73e456d" + "reference": "69436717dc70e30f80d7f8fd02504c22992a9ad5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/c03e649220089f6e5a52d422e24e3f98c73e456d", - "reference": "c03e649220089f6e5a52d422e24e3f98c73e456d", + "url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/69436717dc70e30f80d7f8fd02504c22992a9ad5", + "reference": "69436717dc70e30f80d7f8fd02504c22992a9ad5", "shasum": "" }, "require": { @@ -517,7 +581,7 @@ "php": "^8.0" }, "require-dev": { - "livewire/livewire": "^3.0", + "livewire/livewire": "^3.0|^4.0", "livewire/volt": "^1.3", "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0|^11.0", "phpunit/phpunit": "^9.0|^10.0|^11.5.3|^12.5.12" @@ -550,7 +614,7 @@ "type": "github" } ], - "time": "2026-03-16T11:29:23+00:00" + "time": "2026-08-06T08:41:51+00:00" }, { "name": "dflydev/dot-access-data", @@ -975,19 +1039,20 @@ }, { "name": "filament/actions", - "version": "v5.6.1", + "version": "v5.7.8", "source": { "type": "git", "url": "https://github.com/filamentphp/actions.git", - "reference": "fe5b10e270021e428294cea8381e608780bb243b" + "reference": "cc84d88a7318eb1faae39cb5f36191ccc3b81334" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/actions/zipball/fe5b10e270021e428294cea8381e608780bb243b", - "reference": "fe5b10e270021e428294cea8381e608780bb243b", + "url": "https://api.github.com/repos/filamentphp/actions/zipball/cc84d88a7318eb1faae39cb5f36191ccc3b81334", + "reference": "cc84d88a7318eb1faae39cb5f36191ccc3b81334", "shasum": "" }, "require": { + "anourvalar/eloquent-serialize": "^1.3.6", "filament/forms": "self.version", "filament/infolists": "self.version", "filament/notifications": "self.version", @@ -1019,20 +1084,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2026-04-24T08:57:06+00:00" + "time": "2026-08-31T17:03:02+00:00" }, { "name": "filament/filament", - "version": "v5.6.1", + "version": "v5.7.8", "source": { "type": "git", "url": "https://github.com/filamentphp/panels.git", - "reference": "fcda4f158395b691fd189c4b2a2e68455bb8a96f" + "reference": "16adb1532c639739944a2acea959e16ac100e093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/panels/zipball/fcda4f158395b691fd189c4b2a2e68455bb8a96f", - "reference": "fcda4f158395b691fd189c4b2a2e68455bb8a96f", + "url": "https://api.github.com/repos/filamentphp/panels/zipball/16adb1532c639739944a2acea959e16ac100e093", + "reference": "16adb1532c639739944a2acea959e16ac100e093", "shasum": "" }, "require": { @@ -1047,7 +1112,7 @@ "filament/widgets": "self.version", "php": "^8.2", "pragmarx/google2fa": "^8.0|^9.0", - "pragmarx/google2fa-qrcode": "^3.0" + "pragmarx/google2fa-qrcode": "^3.0|^4.0" }, "type": "library", "extra": { @@ -1076,20 +1141,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2026-04-21T15:37:04+00:00" + "time": "2026-08-31T17:09:06+00:00" }, { "name": "filament/forms", - "version": "v5.6.1", + "version": "v5.7.8", "source": { "type": "git", "url": "https://github.com/filamentphp/forms.git", - "reference": "73571a3ca19bee4c1974e8bb2ba3c022bd284bd6" + "reference": "ea0a0ee11efca648a043f9b3ff4113e54dadb7b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/forms/zipball/73571a3ca19bee4c1974e8bb2ba3c022bd284bd6", - "reference": "73571a3ca19bee4c1974e8bb2ba3c022bd284bd6", + "url": "https://api.github.com/repos/filamentphp/forms/zipball/ea0a0ee11efca648a043f9b3ff4113e54dadb7b0", + "reference": "ea0a0ee11efca648a043f9b3ff4113e54dadb7b0", "shasum": "" }, "require": { @@ -1126,20 +1191,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2026-04-24T08:58:46+00:00" + "time": "2026-08-31T17:04:14+00:00" }, { "name": "filament/infolists", - "version": "v5.6.1", + "version": "v5.7.8", "source": { "type": "git", "url": "https://github.com/filamentphp/infolists.git", - "reference": "39b01e3a86ca0fe0a0c7e45038de9312eefcee51" + "reference": "0a8ca9b6cc60f7179de87b0ad9c2f787bbbd2d29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/infolists/zipball/39b01e3a86ca0fe0a0c7e45038de9312eefcee51", - "reference": "39b01e3a86ca0fe0a0c7e45038de9312eefcee51", + "url": "https://api.github.com/repos/filamentphp/infolists/zipball/0a8ca9b6cc60f7179de87b0ad9c2f787bbbd2d29", + "reference": "0a8ca9b6cc60f7179de87b0ad9c2f787bbbd2d29", "shasum": "" }, "require": { @@ -1171,20 +1236,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2026-04-21T15:38:03+00:00" + "time": "2026-08-31T17:09:57+00:00" }, { "name": "filament/notifications", - "version": "v5.6.1", + "version": "v5.7.8", "source": { "type": "git", "url": "https://github.com/filamentphp/notifications.git", - "reference": "970765bd93b6d5aee2e110e67586f28e3199c0c0" + "reference": "95d7d4730152353fbcd7365ee890022707382de7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/notifications/zipball/970765bd93b6d5aee2e110e67586f28e3199c0c0", - "reference": "970765bd93b6d5aee2e110e67586f28e3199c0c0", + "url": "https://api.github.com/repos/filamentphp/notifications/zipball/95d7d4730152353fbcd7365ee890022707382de7", + "reference": "95d7d4730152353fbcd7365ee890022707382de7", "shasum": "" }, "require": { @@ -1218,20 +1283,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2026-04-24T08:57:15+00:00" + "time": "2026-08-31T17:08:43+00:00" }, { "name": "filament/query-builder", - "version": "v5.6.1", + "version": "v5.7.8", "source": { "type": "git", "url": "https://github.com/filamentphp/query-builder.git", - "reference": "3f1fc2c03649552922954389f81a702180a5166c" + "reference": "900b00b9dd5162c929aee540888c66883e7dd832" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/query-builder/zipball/3f1fc2c03649552922954389f81a702180a5166c", - "reference": "3f1fc2c03649552922954389f81a702180a5166c", + "url": "https://api.github.com/repos/filamentphp/query-builder/zipball/900b00b9dd5162c929aee540888c66883e7dd832", + "reference": "900b00b9dd5162c929aee540888c66883e7dd832", "shasum": "" }, "require": { @@ -1264,20 +1329,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2026-04-21T15:37:23+00:00" + "time": "2026-08-31T17:03:51+00:00" }, { "name": "filament/schemas", - "version": "v5.6.1", + "version": "v5.7.8", "source": { "type": "git", "url": "https://github.com/filamentphp/schemas.git", - "reference": "447b5f0034aab7bbda025ff6b5b177f424468e39" + "reference": "890fa9f369267f8a002ec6ea2313586b1f54cddb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/schemas/zipball/447b5f0034aab7bbda025ff6b5b177f424468e39", - "reference": "447b5f0034aab7bbda025ff6b5b177f424468e39", + "url": "https://api.github.com/repos/filamentphp/schemas/zipball/890fa9f369267f8a002ec6ea2313586b1f54cddb", + "reference": "890fa9f369267f8a002ec6ea2313586b1f54cddb", "shasum": "" }, "require": { @@ -1309,20 +1374,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2026-04-21T15:39:34+00:00" + "time": "2026-08-31T17:09:38+00:00" }, { "name": "filament/support", - "version": "v5.6.1", + "version": "v5.7.8", "source": { "type": "git", "url": "https://github.com/filamentphp/support.git", - "reference": "a16403582ed0a3c74fb1bd11615df9a4f95a155c" + "reference": "db044fa876c998aa55b867f5774a4eb5b42c75ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/support/zipball/a16403582ed0a3c74fb1bd11615df9a4f95a155c", - "reference": "a16403582ed0a3c74fb1bd11615df9a4f95a155c", + "url": "https://api.github.com/repos/filamentphp/support/zipball/db044fa876c998aa55b867f5774a4eb5b42c75ea", + "reference": "db044fa876c998aa55b867f5774a4eb5b42c75ea", "shasum": "" }, "require": { @@ -1367,20 +1432,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2026-04-21T15:41:41+00:00" + "time": "2026-09-01T08:40:00+00:00" }, { "name": "filament/tables", - "version": "v5.6.1", + "version": "v5.7.8", "source": { "type": "git", "url": "https://github.com/filamentphp/tables.git", - "reference": "e8aab9455d07dfb9073127297ba726a72dfb33a4" + "reference": "71c498959168c0a60b6cbe754c216a3a1f0ec623" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/tables/zipball/e8aab9455d07dfb9073127297ba726a72dfb33a4", - "reference": "e8aab9455d07dfb9073127297ba726a72dfb33a4", + "url": "https://api.github.com/repos/filamentphp/tables/zipball/71c498959168c0a60b6cbe754c216a3a1f0ec623", + "reference": "71c498959168c0a60b6cbe754c216a3a1f0ec623", "shasum": "" }, "require": { @@ -1413,20 +1478,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2026-04-21T15:38:41+00:00" + "time": "2026-08-31T17:09:20+00:00" }, { "name": "filament/widgets", - "version": "v5.6.1", + "version": "v5.7.8", "source": { "type": "git", "url": "https://github.com/filamentphp/widgets.git", - "reference": "9327b63e0c3f6a646376a18ed4c8d485c18a8dc6" + "reference": "ce0718e8c0aced904fb9f09be03f4fb33f8dcde6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/widgets/zipball/9327b63e0c3f6a646376a18ed4c8d485c18a8dc6", - "reference": "9327b63e0c3f6a646376a18ed4c8d485c18a8dc6", + "url": "https://api.github.com/repos/filamentphp/widgets/zipball/ce0718e8c0aced904fb9f09be03f4fb33f8dcde6", + "reference": "ce0718e8c0aced904fb9f09be03f4fb33f8dcde6", "shasum": "" }, "require": { @@ -1457,7 +1522,7 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2026-04-21T15:39:57+00:00" + "time": "2026-08-31T17:10:07+00:00" }, { "name": "fruitcake/php-cors", @@ -1610,24 +1675,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.4", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + "reference": "adccca3324eece92ca35463648c12b9e6293c05b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", - "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/adccca3324eece92ca35463648c12b9e6293c05b", + "reference": "adccca3324eece92ca35463648c12b9e6293c05b", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.5" + "phpoption/phpoption": "^1.10" }, "require-dev": { - "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + "phpunit/phpunit": "^8.5.52 || ^9.6.34 || ^10.5.63 || ^11.5.55 || ^12.5.14" }, "type": "library", "autoload": { @@ -1656,7 +1721,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.2.0" }, "funding": [ { @@ -1668,29 +1733,30 @@ "type": "tidelift" } ], - "time": "2025-12-27T19:43:20+00:00" + "time": "2026-08-24T09:06:52+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.10.0", + "version": "7.15.5", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee80339fd9177ba44c49cdb653ff02a4d1106b9a", + "reference": "ee80339fd9177ba44c49cdb653ff02a4d1106b9a", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", + "guzzlehttp/promises": "^2.5.3", + "guzzlehttp/psr7": "^2.13.1", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-client-implementation": "1.0" @@ -1698,9 +1764,10 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "guzzle/client-integration-tests": "3.0.2", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1778,7 +1845,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + "source": "https://github.com/guzzle/guzzle/tree/7.15.5" }, "funding": [ { @@ -1794,28 +1861,29 @@ "type": "tidelift" } ], - "time": "2025-08-23T22:36:01+00:00" + "time": "2026-08-24T09:21:06+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.3.0", + "version": "2.5.3", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "481557b130ef3790cf82b713667b43030dc9c957" + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", - "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "url": "https://api.github.com/repos/guzzle/promises/zipball/cde49999552d185d64715fe9c1f77a2aadd2f9f1", + "reference": "cde49999552d185d64715fe9c1f77a2aadd2f9f1", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "type": "library", "extra": { @@ -1861,7 +1929,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.3.0" + "source": "https://github.com/guzzle/promises/tree/2.5.3" }, "funding": [ { @@ -1877,27 +1945,29 @@ "type": "tidelift" } ], - "time": "2025-08-22T14:34:08+00:00" + "time": "2026-08-24T09:11:28+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.9.0", + "version": "2.13.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + "reference": "95e7828100de18b4e269fb1703be530082d5166d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", - "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/95e7828100de18b4e269fb1703be530082d5166d", + "reference": "95e7828100de18b4e269fb1703be530082d5166d", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-factory-implementation": "1.0", @@ -1905,9 +1975,9 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "0.9.0", + "http-interop/http-factory-tests": "1.1.0", "jshttp/mime-db": "1.54.0.1", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1978,7 +2048,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.9.0" + "source": "https://github.com/guzzle/psr7/tree/2.13.1" }, "funding": [ { @@ -1994,30 +2064,30 @@ "type": "tidelift" } ], - "time": "2026-03-10T16:41:02+00:00" + "time": "2026-08-24T09:13:11+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.5", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" + "reference": "7a466ad606491eb6528c717482f7cca77f1851f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", - "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/7a466ad606491eb6528c717482f7cca77f1851f3", + "reference": "7a466ad606491eb6528c717482f7cca77f1851f3", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0", - "symfony/polyfill-php80": "^1.24" + "php": "^7.4 || ^8.0", + "symfony/polyfill-php80": "^1.25" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25", - "uri-template/tests": "1.0.0" + "phpunit/phpunit": "^9.6.34", + "uri-template/tests": "1.0.2" }, "type": "library", "extra": { @@ -2064,7 +2134,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" + "source": "https://github.com/guzzle/uri-template/tree/v2.0.1" }, "funding": [ { @@ -2080,20 +2150,20 @@ "type": "tidelift" } ], - "time": "2025-08-22T14:27:06+00:00" + "time": "2026-08-24T17:13:02+00:00" }, { "name": "kirschbaum-development/eloquent-power-joins", - "version": "4.3.1", + "version": "4.3.3", "source": { "type": "git", "url": "https://github.com/kirschbaum-development/eloquent-power-joins.git", - "reference": "3f77b096c1e8b5aa1fc40d7080e55e795f3430ae" + "reference": "c609dbbe4ad2051b667e937f1ab554067519d64b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/3f77b096c1e8b5aa1fc40d7080e55e795f3430ae", - "reference": "3f77b096c1e8b5aa1fc40d7080e55e795f3430ae", + "url": "https://api.github.com/repos/kirschbaum-development/eloquent-power-joins/zipball/c609dbbe4ad2051b667e937f1ab554067519d64b", + "reference": "c609dbbe4ad2051b667e937f1ab554067519d64b", "shasum": "" }, "require": { @@ -2141,26 +2211,26 @@ ], "support": { "issues": "https://github.com/kirschbaum-development/eloquent-power-joins/issues", - "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.3.1" + "source": "https://github.com/kirschbaum-development/eloquent-power-joins/tree/4.3.3" }, - "time": "2026-03-29T12:05:03+00:00" + "time": "2026-07-23T11:41:37+00:00" }, { "name": "laravel/framework", - "version": "v13.6.0", + "version": "v13.30.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "416a93ea9c53161e0d4b8a44045f447b65a7d2f1" + "reference": "718d17db56861e0a49f644217c8853dab1bff8ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/416a93ea9c53161e0d4b8a44045f447b65a7d2f1", - "reference": "416a93ea9c53161e0d4b8a44045f447b65a7d2f1", + "url": "https://api.github.com/repos/laravel/framework/zipball/718d17db56861e0a49f644217c8853dab1bff8ce", + "reference": "718d17db56861e0a49f644217c8853dab1bff8ce", "shasum": "" }, "require": { - "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17", + "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.4", @@ -2173,20 +2243,22 @@ "ext-session": "*", "ext-tokenizer": "*", "fruitcake/php-cors": "^1.3", - "guzzlehttp/guzzle": "^7.8.2", - "guzzlehttp/promises": "^2.0.3", - "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.3.0", + "guzzlehttp/guzzle": "^7.8.2 || ^8.0", + "guzzlehttp/promises": "^2.0.3 || ^3.0", + "guzzlehttp/psr7": "^2.9 || ^3.0", + "guzzlehttp/uri-template": "^1.0 || ^2.0", + "laravel/prompts": "^0.3.11", "laravel/serializable-closure": "^2.0.10", "league/commonmark": "^2.8.1", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", - "monolog/monolog": "^3.0", + "monolog/monolog": "^3.10", "nesbot/carbon": "^3.8.4", "nunomaduro/termwind": "^2.0", "php": "^8.3", "psr/container": "^1.1.1 || ^2.0.1", + "psr/http-message": "^1.0 || ^2.0", "psr/log": "^1.0 || ^2.0 || ^3.0", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "ramsey/uuid": "^4.7", @@ -2197,8 +2269,9 @@ "symfony/http-kernel": "^7.4.0 || ^8.0.0", "symfony/mailer": "^7.4.0 || ^8.0.0", "symfony/mime": "^7.4.0 || ^8.0.0", - "symfony/polyfill-php84": "^1.33", - "symfony/polyfill-php85": "^1.33", + "symfony/polyfill-php84": "^1.36", + "symfony/polyfill-php85": "^1.36", + "symfony/polyfill-php86": "^1.36", "symfony/process": "^7.4.5 || ^8.0.5", "symfony/routing": "^7.4.0 || ^8.0.0", "symfony/uid": "^7.4.0 || ^8.0.0", @@ -2234,6 +2307,7 @@ "illuminate/filesystem": "self.version", "illuminate/hashing": "self.version", "illuminate/http": "self.version", + "illuminate/image": "self.version", "illuminate/json-schema": "self.version", "illuminate/log": "self.version", "illuminate/macroable": "self.version", @@ -2259,7 +2333,7 @@ "aws/aws-sdk-php": "^3.322.9", "ext-gmp": "*", "fakerphp/faker": "^1.24", - "guzzlehttp/psr7": "^2.4", + "intervention/image": "^4.0", "laravel/pint": "^1.18", "league/flysystem-aws-s3-v3": "^3.25.1", "league/flysystem-ftp": "^3.25.1", @@ -2274,7 +2348,7 @@ "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^11.5.50 || ^12.5.8 || ^13.0.3", "predis/predis": "^2.3 || ^3.0", - "rector/rector": "^2.3", + "rector/rector": "2.6.3", "resend/resend-php": "^1.0", "symfony/cache": "^7.4.0 || ^8.0.0", "symfony/http-client": "^7.4.0 || ^8.0.0", @@ -2296,6 +2370,7 @@ "ext-redis": "Required to use the Redis cache and queue drivers (^4.0 || ^5.0 || ^6.0).", "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "intervention/image": "Required to use the image processing features (^4.0).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", @@ -2307,7 +2382,6 @@ "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", "phpunit/phpunit": "Required to use assertions and run tests (^11.5.50 || ^12.5.8 || ^13.0.3).", "predis/predis": "Required to use the predis connector (^2.3 || ^3.0).", - "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0 || ^7.0).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0 || ^1.0).", "spatie/fork": "Required to use the 'fork' concurrency driver (^1.2).", @@ -2366,20 +2440,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-04-21T13:32:11+00:00" + "time": "2026-09-01T21:42:30+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.17", + "version": "v0.3.24", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "6a82ac19a28b916ae0885828795dbd4c59d9a818" + "reference": "5d3cdef29e93ca3b62b1871359db3078cd99908b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/6a82ac19a28b916ae0885828795dbd4c59d9a818", - "reference": "6a82ac19a28b916ae0885828795dbd4c59d9a818", + "url": "https://api.github.com/repos/laravel/prompts/zipball/5d3cdef29e93ca3b62b1871359db3078cd99908b", + "reference": "5d3cdef29e93ca3b62b1871359db3078cd99908b", "shasum": "" }, "require": { @@ -2423,22 +2497,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.17" + "source": "https://github.com/laravel/prompts/tree/v0.3.24" }, - "time": "2026-04-20T16:07:33+00:00" + "time": "2026-08-20T12:55:36+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.12", + "version": "v2.0.16", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "a6abb4e54f6fcd3138120b9ad497f0bd146f9919" + "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/a6abb4e54f6fcd3138120b9ad497f0bd146f9919", - "reference": "a6abb4e54f6fcd3138120b9ad497f0bd146f9919", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", + "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", "shasum": "" }, "require": { @@ -2486,20 +2560,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-04-14T13:33:34+00:00" + "time": "2026-08-18T20:28:54+00:00" }, { "name": "league/commonmark", - "version": "2.8.2", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "59fb075d2101740c337c7216e3f32b36c204218b" + "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b", - "reference": "59fb075d2101740c337c7216e3f32b36c204218b", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d2d1aa8b35e072966c89bc0c66cf926e56767dc4", + "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4", "shasum": "" }, "require": { @@ -2521,8 +2595,8 @@ "github/gfm": "0.29.0", "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", - "phpstan/phpstan": "^1.8.2", - "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "phpstan/phpstan": "^2.0.0", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0", "scrutinizer/ocular": "^1.8.1", "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", @@ -2536,7 +2610,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.9-dev" + "dev-main": "2.11-dev" } }, "autoload": { @@ -2593,7 +2667,7 @@ "type": "tidelift" } ], - "time": "2026-03-19T13:16:38+00:00" + "time": "2026-08-11T16:06:25+00:00" }, { "name": "league/config", @@ -2770,16 +2844,16 @@ }, { "name": "league/flysystem", - "version": "3.33.0", + "version": "3.36.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "570b8871e0ce693764434b29154c54b434905350" + "reference": "f7fb152932f30072d573510cbd4dd657d6475b25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/570b8871e0ce693764434b29154c54b434905350", - "reference": "570b8871e0ce693764434b29154c54b434905350", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f7fb152932f30072d573510cbd4dd657d6475b25", + "reference": "f7fb152932f30072d573510cbd4dd657d6475b25", "shasum": "" }, "require": { @@ -2847,22 +2921,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.33.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.36.0" }, - "time": "2026-03-25T07:59:30+00:00" + "time": "2026-09-02T08:00:27+00:00" }, { "name": "league/flysystem-local", - "version": "3.31.0", + "version": "3.35.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", - "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/a099b24dce160f3b2239043d13d47c4a1a214ea4", + "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4", "shasum": "" }, "require": { @@ -2896,22 +2970,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.35.3" }, - "time": "2026-01-23T15:30:45+00:00" + "time": "2026-08-12T13:29:21+00:00" }, { "name": "league/mime-type-detection", - "version": "1.16.0", + "version": "1.17.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76", "shasum": "" }, "require": { @@ -2921,7 +2995,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0" }, "type": "library", "autoload": { @@ -2942,7 +3016,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.17.0" }, "funding": [ { @@ -2954,7 +3028,7 @@ "type": "tidelift" } ], - "time": "2024-09-21T08:32:55+00:00" + "time": "2026-07-09T11:49:27+00:00" }, { "name": "league/uri", @@ -3224,16 +3298,16 @@ }, { "name": "livewire/livewire", - "version": "v4.2.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "7d0bfa46269b1ec186b8cdd38baffee5cc647d10" + "reference": "92f1427022714b34024459167aa6a85d4fb4af44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/7d0bfa46269b1ec186b8cdd38baffee5cc647d10", - "reference": "7d0bfa46269b1ec186b8cdd38baffee5cc647d10", + "url": "https://api.github.com/repos/livewire/livewire/zipball/92f1427022714b34024459167aa6a85d4fb4af44", + "reference": "92f1427022714b34024459167aa6a85d4fb4af44", "shasum": "" }, "require": { @@ -3288,7 +3362,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v4.2.4" + "source": "https://github.com/livewire/livewire/tree/v4.4.3" }, "funding": [ { @@ -3296,7 +3370,7 @@ "type": "github" } ], - "time": "2026-04-02T20:48:35+00:00" + "time": "2026-08-31T15:40:58+00:00" }, { "name": "manukminasyan/blade-mdi", @@ -3379,16 +3453,16 @@ }, { "name": "monolog/monolog", - "version": "3.10.0", + "version": "3.11.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + "reference": "147f303310f06334f03f409e49d7ad1e275ff05a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", - "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/147f303310f06334f03f409e49d7ad1e275ff05a", + "reference": "147f303310f06334f03f409e49d7ad1e275ff05a", "shasum": "" }, "require": { @@ -3466,7 +3540,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + "source": "https://github.com/Seldaek/monolog/tree/3.11.0" }, "funding": [ { @@ -3478,20 +3552,20 @@ "type": "tidelift" } ], - "time": "2026-01-02T08:56:05+00:00" + "time": "2026-09-02T12:39:56+00:00" }, { "name": "nesbot/carbon", - "version": "3.11.4", + "version": "3.13.2", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "e890471a3494740f7d9326d72ce6a8c559ffee60" + "reference": "a1c54919f5fff9800cd03c32bd01defd5a4061cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/e890471a3494740f7d9326d72ce6a8c559ffee60", - "reference": "e890471a3494740f7d9326d72ce6a8c559ffee60", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/a1c54919f5fff9800cd03c32bd01defd5a4061cb", + "reference": "a1c54919f5fff9800cd03c32bd01defd5a4061cb", "shasum": "" }, "require": { @@ -3583,7 +3657,7 @@ "type": "tidelift" } ], - "time": "2026-04-07T09:57:54+00:00" + "time": "2026-08-08T11:40:35+00:00" }, { "name": "nette/php-generator", @@ -3661,16 +3735,16 @@ }, { "name": "nette/schema", - "version": "v1.3.5", + "version": "v1.3.6", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + "reference": "c54350438cd6914616f790a49cb424605f421562" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", - "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "url": "https://api.github.com/repos/nette/schema/zipball/c54350438cd6914616f790a49cb424605f421562", + "reference": "c54350438cd6914616f790a49cb424605f421562", "shasum": "" }, "require": { @@ -3722,22 +3796,22 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.5" + "source": "https://github.com/nette/schema/tree/v1.3.6" }, - "time": "2026-02-23T03:47:12+00:00" + "time": "2026-08-16T21:58:41+00:00" }, { "name": "nette/utils", - "version": "v4.1.3", + "version": "v4.1.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", - "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", + "url": "https://api.github.com/repos/nette/utils/zipball/b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0", "shasum": "" }, "require": { @@ -3757,7 +3831,7 @@ }, "suggest": { "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-iconv": "to use Strings::chr(), ord() and reverse()", "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", @@ -3813,26 +3887,25 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.3" + "source": "https://github.com/nette/utils/tree/v4.1.5" }, - "time": "2026-02-13T03:05:33+00:00" + "time": "2026-07-17T23:02:45+00:00" }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -3871,9 +3944,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-07-04T14:30:18+00:00" }, { "name": "nunomaduro/termwind", @@ -4373,16 +4446,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.5", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", - "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d", "shasum": "" }, "require": { @@ -4390,7 +4463,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + "phpunit/phpunit": "^8.5.54 || ^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.33" }, "type": "library", "extra": { @@ -4432,7 +4505,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + "source": "https://github.com/schmittjoh/php-option/tree/1.10.0" }, "funding": [ { @@ -4444,20 +4517,20 @@ "type": "tidelift" } ], - "time": "2025-12-27T19:41:33+00:00" + "time": "2026-08-24T00:54:40+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.2", + "version": "2.3.5", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/148cefffaf0233e4c08cc13db8a195a56dd6dfe9", + "reference": "148cefffaf0233e4c08cc13db8a195a56dd6dfe9", "shasum": "" }, "require": { @@ -4489,22 +4562,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.5" }, - "time": "2026-01-25T14:56:51+00:00" + "time": "2026-08-31T16:05:28+00:00" }, { "name": "pragmarx/google2fa", - "version": "v9.0.0", + "version": "v9.1.0", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf" + "reference": "f00bc788c555adfb6765c437ff3538e59cd88af1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/e6bc62dd6ae83acc475f57912e27466019a1f2cf", - "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/f00bc788c555adfb6765c437ff3538e59cd88af1", + "reference": "f00bc788c555adfb6765c437ff3538e59cd88af1", "shasum": "" }, "require": { @@ -4512,8 +4585,11 @@ "php": "^7.1|^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^7.5.15|^8.5|^9.0" + "phpstan/phpstan": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.0|^2.0", + "phpunit/phpunit": "~9|~10|~11|~12|~13", + "psalm/plugin-phpunit": "^0.19|^0.20", + "vimeo/psalm": "^5.26|^6.13" }, "type": "library", "autoload": { @@ -4536,38 +4612,48 @@ "keywords": [ "2fa", "Authentication", + "MFA", "Two Factor Authentication", - "google2fa" + "google-authenticator", + "google2fa", + "hotp", + "otp", + "rfc4226", + "rfc6238", + "totp" ], "support": { + "docs": "https://github.com/antonioribeiro/google2fa#readme", "issues": "https://github.com/antonioribeiro/google2fa/issues", - "source": "https://github.com/antonioribeiro/google2fa/tree/v9.0.0" + "security": "https://github.com/antonioribeiro/google2fa/security/policy", + "source": "https://github.com/antonioribeiro/google2fa" }, - "time": "2025-09-19T22:51:08+00:00" + "time": "2026-08-15T13:22:01+00:00" }, { "name": "pragmarx/google2fa-qrcode", - "version": "v3.0.0", + "version": "v4.0.0", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa-qrcode.git", - "reference": "ce4d8a729b6c93741c607cfb2217acfffb5bf76b" + "reference": "16159f84fa0838c276f35d46de57fd90dfbb385c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa-qrcode/zipball/ce4d8a729b6c93741c607cfb2217acfffb5bf76b", - "reference": "ce4d8a729b6c93741c607cfb2217acfffb5bf76b", + "url": "https://api.github.com/repos/antonioribeiro/google2fa-qrcode/zipball/16159f84fa0838c276f35d46de57fd90dfbb385c", + "reference": "16159f84fa0838c276f35d46de57fd90dfbb385c", "shasum": "" }, "require": { - "php": ">=7.1", - "pragmarx/google2fa": ">=4.0" + "php": "^8.1", + "pragmarx/google2fa": "^8.0|^9.0" }, "require-dev": { - "bacon/bacon-qr-code": "^2.0", - "chillerlan/php-qrcode": "^1.0|^2.0|^3.0|^4.0", + "bacon/bacon-qr-code": "^2.0|^3.0", + "chillerlan/php-qrcode": "^5.0|^6.0", "khanamiryan/qrcode-detector-decoder": "^1.0", - "phpunit/phpunit": "~4|~5|~6|~7|~8|~9" + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "~9|~10|~11|~12|~13" }, "suggest": { "bacon/bacon-qr-code": "For QR Code generation, requires imagick", @@ -4608,9 +4694,9 @@ ], "support": { "issues": "https://github.com/antonioribeiro/google2fa-qrcode/issues", - "source": "https://github.com/antonioribeiro/google2fa-qrcode/tree/v3.0.0" + "source": "https://github.com/antonioribeiro/google2fa-qrcode/tree/v4.0.0" }, - "time": "2021-08-15T12:53:48+00:00" + "time": "2026-05-08T19:24:44+00:00" }, { "name": "propaganistas/laravel-phone", @@ -5218,20 +5304,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.2", + "version": "4.9.3", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "8429c78ca35a09f27565311b98101e2826affde0" + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", - "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", "shasum": "" }, "require": { - "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": ">=0.8.16 <=0.18", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -5290,9 +5376,9 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.2" + "source": "https://github.com/ramsey/uuid/tree/4.9.3" }, - "time": "2025-12-14T04:43:48+00:00" + "time": "2026-06-18T03:57:49+00:00" }, { "name": "ryangjchandler/blade-capture-directive", @@ -5452,16 +5538,16 @@ }, { "name": "spatie/invade", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/spatie/invade.git", - "reference": "b920f6411d21df4e8610a138e2e87ae4957d7f63" + "reference": "f929c7b6e75fceeefa7b41a491e91274bafb17af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/invade/zipball/b920f6411d21df4e8610a138e2e87ae4957d7f63", - "reference": "b920f6411d21df4e8610a138e2e87ae4957d7f63", + "url": "https://api.github.com/repos/spatie/invade/zipball/f929c7b6e75fceeefa7b41a491e91274bafb17af", + "reference": "f929c7b6e75fceeefa7b41a491e91274bafb17af", "shasum": "" }, "require": { @@ -5499,7 +5585,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/invade/tree/2.1.0" + "source": "https://github.com/spatie/invade/tree/2.1.1" }, "funding": [ { @@ -5507,7 +5593,7 @@ "type": "github" } ], - "time": "2024-05-17T09:06:10+00:00" + "time": "2026-08-24T12:39:05+00:00" }, { "name": "spatie/laravel-data", @@ -5593,16 +5679,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.93.0", + "version": "1.93.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7" + "reference": "e927d5b5b05e9fecae098e559e51918aa608ad02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", - "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/e927d5b5b05e9fecae098e559e51918aa608ad02", + "reference": "e927d5b5b05e9fecae098e559e51918aa608ad02", "shasum": "" }, "require": { @@ -5642,7 +5728,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.0" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.2" }, "funding": [ { @@ -5650,7 +5736,7 @@ "type": "github" } ], - "time": "2026-02-21T12:49:54+00:00" + "time": "2026-08-26T09:13:25+00:00" }, { "name": "spatie/php-structure-discoverer", @@ -5798,20 +5884,20 @@ }, { "name": "symfony/clock", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "b55a638b189a6faa875e0ccdb00908fb87af95b3" + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/b55a638b189a6faa875e0ccdb00908fb87af95b3", - "reference": "b55a638b189a6faa875e0ccdb00908fb87af95b3", + "url": "https://api.github.com/repos/symfony/clock/zipball/701ef4de9705d6c32292ebee5e8044094a09fbf6", + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/clock": "^1.0" }, "provide": { @@ -5851,7 +5937,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v8.0.8" + "source": "https://github.com/symfony/clock/tree/v8.1.0" }, "funding": [ { @@ -5871,27 +5957,33 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/console", - "version": "v8.0.8", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7" + "reference": "eb7d9957d66739649e931ce7a9d05dab69f8abac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5b66d385dc58f69652e56f78a4184615e3f2b7f7", - "reference": "5b66d385dc58f69652e56f78a4184615e3f2b7f7", + "url": "https://api.github.com/repos/symfony/console/zipball/eb7d9957d66739649e931ce7a9d05dab69f8abac", + "reference": "eb7d9957d66739649e931ce7a9d05dab69f8abac", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php85": "^1.32", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^7.4|^8.0" + "symfony/string": "^7.4.6|^8.0.6" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", + "symfony/event-dispatcher": "<8.1" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" @@ -5899,14 +5991,18 @@ "require-dev": { "psr/log": "^1|^2|^3", "symfony/config": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", - "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/event-dispatcher": "^8.1", + "symfony/filesystem": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", "symfony/http-kernel": "^7.4|^8.0", "symfony/lock": "^7.4|^8.0", "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", "symfony/process": "^7.4|^8.0", "symfony/stopwatch": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", "symfony/var-dumper": "^7.4|^8.0" }, "type": "library", @@ -5941,7 +6037,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v8.0.8" + "source": "https://github.com/symfony/console/tree/v8.1.6" }, "funding": [ { @@ -5961,24 +6057,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-25T14:18:42+00:00" }, { "name": "symfony/css-selector", - "version": "v8.0.8", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "8db1c00226a94d8ab6aa89d9224eeee91e2ea2ed" + "reference": "08e2905152a39cf3fd1745d83f8c483e258887d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/8db1c00226a94d8ab6aa89d9224eeee91e2ea2ed", - "reference": "8db1c00226a94d8ab6aa89d9224eeee91e2ea2ed", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/08e2905152a39cf3fd1745d83f8c483e258887d9", + "reference": "08e2905152a39cf3fd1745d83f8c483e258887d9", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "type": "library", "autoload": { @@ -6010,7 +6106,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v8.0.8" + "source": "https://github.com/symfony/css-selector/tree/v8.1.6" }, "funding": [ { @@ -6030,20 +6126,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-23T10:06:25+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -6056,7 +6152,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -6081,7 +6177,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -6092,29 +6188,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/error-handler", - "version": "v8.0.8", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "c1119fe8dcfc3825ec74ec061b96ef0c8f281517" + "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/c1119fe8dcfc3825ec74ec061b96ef0c8f281517", - "reference": "c1119fe8dcfc3825ec74ec061b96ef0c8f281517", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", + "reference": "8b2a4289ffe5e2dc8fcf645b8e7870e1fa0325ce", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/log": "^1|^2|^3", "symfony/polyfill-php85": "^1.32", "symfony/var-dumper": "^7.4|^8.0" @@ -6158,7 +6258,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v8.0.8" + "source": "https://github.com/symfony/error-handler/tree/v8.1.5" }, "funding": [ { @@ -6178,24 +6278,25 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v8.0.8", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6" + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f662acc6ab22a3d6d716dcb44c381c6002940df6", - "reference": "f662acc6ab22a3d6d716dcb44c381c6002940df6", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7458da64220376b2e0dc2d8451bf43382c1ad297", + "reference": "7458da64220376b2e0dc2d8451bf43382c1ad297", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { @@ -6243,7 +6344,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v8.1.5" }, "funding": [ { @@ -6263,20 +6364,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { @@ -6290,7 +6391,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -6323,7 +6424,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { @@ -6334,29 +6435,33 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/finder", - "version": "v8.0.8", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8da41214757b87d97f181e3d14a4179286151007" + "reference": "8d7acede2b2ae07605783d1c43e49b5767036474" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8da41214757b87d97f181e3d14a4179286151007", - "reference": "8da41214757b87d97f181e3d14a4179286151007", + "url": "https://api.github.com/repos/symfony/finder/zipball/8d7acede2b2ae07605783d1c43e49b5767036474", + "reference": "8d7acede2b2ae07605783d1c43e49b5767036474", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "require-dev": { "symfony/filesystem": "^7.4|^8.0" @@ -6387,7 +6492,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v8.0.8" + "source": "https://github.com/symfony/finder/tree/v8.1.5" }, "funding": [ { @@ -6407,26 +6512,26 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-21T12:16:08+00:00" }, { "name": "symfony/html-sanitizer", - "version": "v8.0.8", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/html-sanitizer.git", - "reference": "b0e4a2d9a82ab6bdcc742a63398781f6dae64fe5" + "reference": "f8bbdb0704e6b6e9a3412481c1a87886a0633199" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/b0e4a2d9a82ab6bdcc742a63398781f6dae64fe5", - "reference": "b0e4a2d9a82ab6bdcc742a63398781f6dae64fe5", + "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/f8bbdb0704e6b6e9a3412481c1a87886a0633199", + "reference": "f8bbdb0704e6b6e9a3412481c1a87886a0633199", "shasum": "" }, "require": { "ext-dom": "*", "league/uri": "^6.5|^7.0", - "php": ">=8.4" + "php": ">=8.4.1" }, "type": "library", "autoload": { @@ -6459,7 +6564,7 @@ "sanitizer" ], "support": { - "source": "https://github.com/symfony/html-sanitizer/tree/v8.0.8" + "source": "https://github.com/symfony/html-sanitizer/tree/v8.1.6" }, "funding": [ { @@ -6479,24 +6584,25 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-30T01:03:44+00:00" }, { "name": "symfony/http-foundation", - "version": "v8.0.8", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "02656f7ebeae5c155d659e946f6b3a33df24051b" + "reference": "093b78326f649c3a9db922b9f17123b6aeb3b8fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/02656f7ebeae5c155d659e946f6b3a33df24051b", - "reference": "02656f7ebeae5c155d659e946f6b3a33df24051b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/093b78326f649c3a9db922b9f17123b6aeb3b8fb", + "reference": "093b78326f649c3a9db922b9f17123b6aeb3b8fb", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "^1.1" }, "conflict": { @@ -6539,7 +6645,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v8.0.8" + "source": "https://github.com/symfony/http-foundation/tree/v8.1.6" }, "funding": [ { @@ -6559,34 +6665,39 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-30T20:10:55+00:00" }, { "name": "symfony/http-kernel", - "version": "v8.0.8", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "1770f6818d83b2fddc12185025b93f39a90cb628" + "reference": "2f73beb7c6f1a97d2c17bbf4dbd59da8cc18b355" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1770f6818d83b2fddc12185025b93f39a90cb628", - "reference": "1770f6818d83b2fddc12185025b93f39a90cb628", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/2f73beb7c6f1a97d2c17bbf4dbd59da8cc18b355", + "reference": "2f73beb7c6f1a97d2c17bbf4dbd59da8cc18b355", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^7.4|^8.0", "symfony/event-dispatcher": "^7.4|^8.0", "symfony/http-foundation": "^7.4|^8.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { + "symfony/dependency-injection": "<8.1", "symfony/flex": "<2.10", "symfony/http-client-contracts": "<2.5", + "symfony/serializer": "<7.4.15|>=8.0,<8.0.15|>=8.1,<8.1.2", "symfony/translation-contracts": "<2.5", + "symfony/var-dumper": "<8.1", + "symfony/web-profiler-bundle": "<8.1", "twig/twig": "<3.21" }, "provide": { @@ -6599,13 +6710,14 @@ "symfony/config": "^7.4|^8.0", "symfony/console": "^7.4|^8.0", "symfony/css-selector": "^7.4|^8.0", - "symfony/dependency-injection": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", "symfony/dom-crawler": "^7.4|^8.0", "symfony/expression-language": "^7.4|^8.0", "symfony/finder": "^7.4|^8.0", "symfony/http-client-contracts": "^2.5|^3", "symfony/process": "^7.4|^8.0", "symfony/property-access": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", "symfony/routing": "^7.4|^8.0", "symfony/serializer": "^7.4|^8.0", "symfony/stopwatch": "^7.4|^8.0", @@ -6613,9 +6725,9 @@ "symfony/translation-contracts": "^2.5|^3", "symfony/uid": "^7.4|^8.0", "symfony/validator": "^7.4|^8.0", - "symfony/var-dumper": "^7.4|^8.0", + "symfony/var-dumper": "^8.1", "symfony/var-exporter": "^7.4|^8.0", - "twig/twig": "^3.21" + "twig/twig": "^3.21|^4.0" }, "type": "library", "autoload": { @@ -6643,7 +6755,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v8.0.8" + "source": "https://github.com/symfony/http-kernel/tree/v8.1.6" }, "funding": [ { @@ -6663,25 +6775,25 @@ "type": "tidelift" } ], - "time": "2026-03-31T21:14:05+00:00" + "time": "2026-08-30T21:40:49+00:00" }, { "name": "symfony/mailer", - "version": "v8.0.8", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "ca5f6edaf8780ece814404b58a4482b22b509c56" + "reference": "89f43137da74b8f1aab37c99926482b7084f51b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/ca5f6edaf8780ece814404b58a4482b22b509c56", - "reference": "ca5f6edaf8780ece814404b58a4482b22b509c56", + "url": "https://api.github.com/repos/symfony/mailer/zipball/89f43137da74b8f1aab37c99926482b7084f51b9", + "reference": "89f43137da74b8f1aab37c99926482b7084f51b9", "shasum": "" }, "require": { "egulias/email-validator": "^2.1.10|^3|^4", - "php": ">=8.4", + "php": ">=8.4.1", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", "symfony/event-dispatcher": "^7.4|^8.0", @@ -6723,7 +6835,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v8.0.8" + "source": "https://github.com/symfony/mailer/tree/v8.1.5" }, "funding": [ { @@ -6743,24 +6855,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/mime", - "version": "v8.0.8", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ddff21f14c7ce04b98101b399a9463dce8b0ce66" + "reference": "1b36ccfd7ccb9ad1d6eafb9024b3dd3d9606b15f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ddff21f14c7ce04b98101b399a9463dce8b0ce66", - "reference": "ddff21f14c7ce04b98101b399a9463dce8b0ce66", + "url": "https://api.github.com/repos/symfony/mime/zipball/1b36ccfd7ccb9ad1d6eafb9024b3dd3d9606b15f", + "reference": "1b36ccfd7ccb9ad1d6eafb9024b3dd3d9606b15f", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -6777,7 +6889,7 @@ "symfony/process": "^7.4|^8.0", "symfony/property-access": "^7.4|^8.0", "symfony/property-info": "^7.4|^8.0", - "symfony/serializer": "^7.4|^8.0" + "symfony/serializer": "^7.4.17|^8.1.5" }, "type": "library", "autoload": { @@ -6809,7 +6921,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v8.0.8" + "source": "https://github.com/symfony/mime/tree/v8.1.6" }, "funding": [ { @@ -6829,7 +6941,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-22T09:06:25+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6916,16 +7028,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.37.0", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { @@ -6974,7 +7086,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { @@ -6994,20 +7106,20 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:13:48+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.37.0", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/51b5ff5ba85452b31ec6f55490b08148612339d9", + "reference": "51b5ff5ba85452b31ec6f55490b08148612339d9", "shasum": "" }, "require": { @@ -7061,7 +7173,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.42.0" }, "funding": [ { @@ -7081,20 +7193,20 @@ "type": "tidelift" } ], - "time": "2024-09-10T14:38:51+00:00" + "time": "2026-08-24T10:51:20+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.37.0", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/aa20edea75bd9c48cfecc8360922e5a6e5c44502", + "reference": "aa20edea75bd9c48cfecc8360922e5a6e5c44502", "shasum": "" }, "require": { @@ -7146,7 +7258,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.42.0" }, "funding": [ { @@ -7166,20 +7278,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-08-07T06:33:24+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.37.0", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315" + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315", - "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { @@ -7231,7 +7343,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -7251,7 +7363,7 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { "name": "symfony/polyfill-php80", @@ -7339,16 +7451,16 @@ }, { "name": "symfony/polyfill-php84", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06" + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/88486db2c389b290bf87ff1de7ebc1e13e42bb06", - "reference": "88486db2c389b290bf87ff1de7ebc1e13e42bb06", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", "shasum": "" }, "require": { @@ -7395,7 +7507,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" }, "funding": [ { @@ -7415,20 +7527,20 @@ "type": "tidelift" } ], - "time": "2026-04-10T18:47:49+00:00" + "time": "2026-05-26T12:51:13+00:00" }, { "name": "symfony/polyfill-php85", - "version": "v1.37.0", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee" + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/fcfa4973a9917cef23f2e38774da74a2b7d115ee", - "reference": "fcfa4973a9917cef23f2e38774da74a2b7d115ee", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/255fab485aaa1006ed411040c42aecd7b5302d7a", + "reference": "255fab485aaa1006ed411040c42aecd7b5302d7a", "shasum": "" }, "require": { @@ -7475,7 +7587,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.41.0" }, "funding": [ { @@ -7495,7 +7607,87 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:10:57+00:00" + "time": "2026-07-01T12:47:55+00:00" + }, + { + "name": "symfony/polyfill-php86", + "version": "v1.41.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php86.git", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "reference": "6bc356ed3d8dbfeea8f0de235e34d670704e880e", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php86\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php86/tree/v1.41.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-07-02T13:42:24+00:00" }, { "name": "symfony/polyfill-uuid", @@ -7582,20 +7774,20 @@ }, { "name": "symfony/process", - "version": "v8.0.8", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc" + "reference": "d863f5e70d7c87abb906ac11b61f83036093000b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc", - "reference": "cb8939aff03470d1a9d1d1b66d08c6fa71b3bbdc", + "url": "https://api.github.com/repos/symfony/process/zipball/d863f5e70d7c87abb906ac11b61f83036093000b", + "reference": "d863f5e70d7c87abb906ac11b61f83036093000b", "shasum": "" }, "require": { - "php": ">=8.4" + "php": ">=8.4.1" }, "type": "library", "autoload": { @@ -7623,7 +7815,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v8.0.8" + "source": "https://github.com/symfony/process/tree/v8.1.6" }, "funding": [ { @@ -7643,24 +7835,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/routing", - "version": "v8.0.8", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0de330ec2ea922a7b08ec45615bd51179de7fda4" + "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0de330ec2ea922a7b08ec45615bd51179de7fda4", - "reference": "0de330ec2ea922a7b08ec45615bd51179de7fda4", + "url": "https://api.github.com/repos/symfony/routing/zipball/3c188091b6b4fa2e4bc83a135caede12deb8576c", + "reference": "3c188091b6b4fa2e4bc83a135caede12deb8576c", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { @@ -7703,7 +7895,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v8.0.8" + "source": "https://github.com/symfony/routing/tree/v8.1.6" }, "funding": [ { @@ -7723,20 +7915,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-17T13:18:34+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.3", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", + "reference": "15e6a07ec2a2c75ceb1b21dd98105ee8456d2257", "shasum": "" }, "require": { @@ -7754,7 +7946,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7790,7 +7982,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.3" }, "funding": [ { @@ -7810,24 +8002,24 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-07-27T15:39:01+00:00" }, { "name": "symfony/string", - "version": "v8.0.8", + "version": "v8.1.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ae9488f874d7603f9d2dfbf120203882b645d963" + "reference": "286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ae9488f874d7603f9d2dfbf120203882b645d963", - "reference": "ae9488f874d7603f9d2dfbf120203882b645d963", + "url": "https://api.github.com/repos/symfony/string/zipball/286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc", + "reference": "286a76b7255e5cc4bf0101a0bc5388ecf1c38ccc", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-intl-grapheme": "^1.33", "symfony/polyfill-intl-normalizer": "^1.0", @@ -7880,7 +8072,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.0.8" + "source": "https://github.com/symfony/string/tree/v8.1.2" }, "funding": [ { @@ -7900,24 +8092,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-07-28T07:35:25+00:00" }, { "name": "symfony/translation", - "version": "v8.0.8", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "27c03ae3940de24ba2f71cfdbac824f2aa1fdf2f" + "reference": "d9e1caba0d6b6f9a26710af8a2f88d37f001215a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/27c03ae3940de24ba2f71cfdbac824f2aa1fdf2f", - "reference": "27c03ae3940de24ba2f71cfdbac824f2aa1fdf2f", + "url": "https://api.github.com/repos/symfony/translation/zipball/d9e1caba0d6b6f9a26710af8a2f88d37f001215a", + "reference": "d9e1caba0d6b6f9a26710af8a2f88d37f001215a", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-mbstring": "^1.0", "symfony/translation-contracts": "^3.6.1" }, @@ -7973,7 +8165,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v8.0.8" + "source": "https://github.com/symfony/translation/tree/v8.1.5" }, "funding": [ { @@ -7993,20 +8185,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-21T17:47:34+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.6.1", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", "shasum": "" }, "require": { @@ -8019,7 +8211,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -8055,7 +8247,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.7.1" }, "funding": [ { @@ -8075,24 +8267,24 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/uid", - "version": "v8.0.8", + "version": "v8.1.5", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "f63fa6096a24147283bce4d29327d285326438e0" + "reference": "a08aef47989093f32fe50fd11859be1b427df389" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/f63fa6096a24147283bce4d29327d285326438e0", - "reference": "f63fa6096a24147283bce4d29327d285326438e0", + "url": "https://api.github.com/repos/symfony/uid/zipball/a08aef47989093f32fe50fd11859be1b427df389", + "reference": "a08aef47989093f32fe50fd11859be1b427df389", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-uuid": "^1.15" }, "require-dev": { @@ -8133,7 +8325,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v8.0.8" + "source": "https://github.com/symfony/uid/tree/v8.1.5" }, "funding": [ { @@ -8153,24 +8345,24 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-11T13:39:01+00:00" }, { "name": "symfony/var-dumper", - "version": "v8.0.8", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "cfb7badd53bf4177f6e9416cfbbccc13c0e773a1" + "reference": "3783365b58972f4779254d98372af80fbf15e170" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cfb7badd53bf4177f6e9416cfbbccc13c0e773a1", - "reference": "cfb7badd53bf4177f6e9416cfbbccc13c0e773a1", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3783365b58972f4779254d98372af80fbf15e170", + "reference": "3783365b58972f4779254d98372af80fbf15e170", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-mbstring": "^1.0" }, "conflict": { @@ -8182,7 +8374,7 @@ "symfony/http-kernel": "^7.4|^8.0", "symfony/process": "^7.4|^8.0", "symfony/uid": "^7.4|^8.0", - "twig/twig": "^3.12" + "twig/twig": "^3.12|^4.0" }, "bin": [ "Resources/bin/var-dump-server" @@ -8220,7 +8412,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v8.0.8" + "source": "https://github.com/symfony/var-dumper/tree/v8.1.6" }, "funding": [ { @@ -8240,7 +8432,7 @@ "type": "tidelift" } ], - "time": "2026-03-31T07:15:36+00:00" + "time": "2026-08-30T20:10:55+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8299,16 +8491,16 @@ }, { "name": "ueberdosis/tiptap-php", - "version": "2.1.0", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/ueberdosis/tiptap-php.git", - "reference": "6ea321fa665080e1a72ac5f52dfab19f6a292e2d" + "reference": "5a2e8155c5b09c9ad4efd480550270a6924865b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ueberdosis/tiptap-php/zipball/6ea321fa665080e1a72ac5f52dfab19f6a292e2d", - "reference": "6ea321fa665080e1a72ac5f52dfab19f6a292e2d", + "url": "https://api.github.com/repos/ueberdosis/tiptap-php/zipball/5a2e8155c5b09c9ad4efd480550270a6924865b9", + "reference": "5a2e8155c5b09c9ad4efd480550270a6924865b9", "shasum": "" }, "require": { @@ -8348,7 +8540,7 @@ ], "support": { "issues": "https://github.com/ueberdosis/tiptap-php/issues", - "source": "https://github.com/ueberdosis/tiptap-php/tree/2.1.0" + "source": "https://github.com/ueberdosis/tiptap-php/tree/2.2.0" }, "funding": [ { @@ -8364,27 +8556,27 @@ "type": "open_collective" } ], - "time": "2026-01-10T16:40:02+00:00" + "time": "2026-08-31T07:00:09+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v5.6.3", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "955e7815d677a3eaa7075231212f2110983adecc" + "reference": "301c07936b16d88628b126b01d082ba153cf4c40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", - "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/301c07936b16d88628b126b01d082ba153cf4c40", + "reference": "301c07936b16d88628b126b01d082ba153cf4c40", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.4", + "graham-campbell/result-type": "^1.2", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.5", + "phpoption/phpoption": "^1.10", "symfony/polyfill-ctype": "^1.26", "symfony/polyfill-mbstring": "^1.26", "symfony/polyfill-php80": "^1.26" @@ -8428,7 +8620,7 @@ "homepage": "https://github.com/vlucas" } ], - "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "description": "Loads environment variables from `.env` to `$_ENV` and `$_SERVER` automagically, and optionally to `getenv()`.", "keywords": [ "dotenv", "env", @@ -8436,7 +8628,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.7.0" }, "funding": [ { @@ -8448,7 +8640,7 @@ "type": "tidelift" } ], - "time": "2025-12-27T19:49:13+00:00" + "time": "2026-08-24T18:07:49+00:00" }, { "name": "voku/portable-ascii", @@ -9103,20 +9295,19 @@ }, { "name": "larastan/larastan", - "version": "v3.9.6", + "version": "v3.11.0", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "9ad17e83e96b63536cb6ac39c3d40d29ff9cf636" + "reference": "9baa74074f17cc70feaef31616a06ea0faeebba5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/9ad17e83e96b63536cb6ac39c3d40d29ff9cf636", - "reference": "9ad17e83e96b63536cb6ac39c3d40d29ff9cf636", + "url": "https://api.github.com/repos/larastan/larastan/zipball/9baa74074f17cc70feaef31616a06ea0faeebba5", + "reference": "9baa74074f17cc70feaef31616a06ea0faeebba5", "shasum": "" }, "require": { - "ext-json": "*", "iamcal/sql-parser": "^0.7.0", "illuminate/console": "^11.44.2 || ^12.4.1 || ^13", "illuminate/container": "^11.44.2 || ^12.4.1 || ^13", @@ -9126,17 +9317,17 @@ "illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13", "illuminate/support": "^11.44.2 || ^12.4.1 || ^13", "php": "^8.2", - "phpstan/phpstan": "^2.1.44" + "phpstan/phpstan": "^2.2.2" }, "require-dev": { - "doctrine/coding-standard": "^13", + "doctrine/coding-standard": "^14", "laravel/framework": "^11.44.2 || ^12.7.2 || ^13", "mockery/mockery": "^1.6.12", "nikic/php-parser": "^5.4", "orchestra/canvas": "^v9.2.2 || ^10.0.1 || ^11", "orchestra/testbench-core": "^9.12.0 || ^10.1 || ^11", "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8" + "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8 || ^13.3.0" }, "suggest": { "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", @@ -9181,7 +9372,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.9.6" + "source": "https://github.com/larastan/larastan/tree/v3.11.0" }, "funding": [ { @@ -9189,7 +9380,7 @@ "type": "github" } ], - "time": "2026-04-16T10:02:43+00:00" + "time": "2026-09-01T16:35:48+00:00" }, { "name": "laravel/pail", @@ -10915,11 +11106,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.52", + "version": "2.2.13", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/08a34f8db7ca4daabff74a474fe13c0e56e2b4e5", - "reference": "08a34f8db7ca4daabff74a474fe13c0e56e2b4e5", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9ba9ac76ee9c5cf5b56d58eb5deec6315b7a0260", + "reference": "9ba9ac76ee9c5cf5b56d58eb5deec6315b7a0260", "shasum": "" }, "require": { @@ -10942,6 +11133,17 @@ "license": [ "MIT" ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ "dev", @@ -10964,7 +11166,7 @@ "type": "github" } ], - "time": "2026-04-28T12:17:53+00:00" + "time": "2026-09-03T20:38:19+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -11018,21 +11220,22 @@ }, { "name": "phpstan/phpstan-phpunit", - "version": "2.0.16", + "version": "2.0.18", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-phpunit.git", - "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32" + "reference": "f5dc20ff8082d02339b60cab68ec3eb0d859fb30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/6ab598e1bc106e6827fd346ae4a12b4a5d634c32", - "reference": "6ab598e1bc106e6827fd346ae4a12b4a5d634c32", + "url": "https://api.github.com/repos/phpstan/phpstan-phpunit/zipball/f5dc20ff8082d02339b60cab68ec3eb0d859fb30", + "reference": "f5dc20ff8082d02339b60cab68ec3eb0d859fb30", "shasum": "" }, "require": { + "phar-io/version": "^3.2", "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.32" + "phpstan/phpstan": "^2.2.3" }, "conflict": { "phpunit/phpunit": "<7.0" @@ -11042,7 +11245,8 @@ "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^9.6", + "shipmonk/name-collision-detector": "^2.1" }, "type": "phpstan-extension", "extra": { @@ -11068,9 +11272,9 @@ ], "support": { "issues": "https://github.com/phpstan/phpstan-phpunit/issues", - "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.16" + "source": "https://github.com/phpstan/phpstan-phpunit/tree/2.0.18" }, - "time": "2026-02-14T09:05:21+00:00" + "time": "2026-07-04T12:16:09+00:00" }, { "name": "phpunit/php-code-coverage", @@ -11588,21 +11792,21 @@ }, { "name": "rector/rector", - "version": "2.4.2", + "version": "2.6.6", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946" + "reference": "ca069d6c79feaa6651b423c15d101a27a436093e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/e645b6463c6a88ea5b44b17d3387d35a912c7946", - "reference": "e645b6463c6a88ea5b44b17d3387d35a912c7946", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/ca069d6c79feaa6651b423c15d101a27a436093e", + "reference": "ca069d6c79feaa6651b423c15d101a27a436093e", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.48" + "phpstan/phpstan": "^2.2.10" }, "conflict": { "rector/rector-doctrine": "*", @@ -11610,9 +11814,6 @@ "rector/rector-phpunit": "*", "rector/rector-symfony": "*" }, - "suggest": { - "ext-dom": "To manipulate phpunit.xml via the custom-rule command" - }, "bin": [ "bin/rector" ], @@ -11636,7 +11837,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.4.2" + "source": "https://github.com/rectorphp/rector/tree/2.6.6" }, "funding": [ { @@ -11644,7 +11845,7 @@ "type": "github" } ], - "time": "2026-04-16T13:07:34+00:00" + "time": "2026-09-02T09:38:46+00:00" }, { "name": "sebastian/cli-parser", @@ -11867,24 +12068,24 @@ }, { "name": "sebastian/diff", - "version": "7.0.0", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", - "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6", + "reference": "cd4cabe39f8a4e8ee6818ba99f10a05561ea4ad6", "shasum": "" }, "require": { "php": ">=8.3" }, "require-dev": { - "phpunit/phpunit": "^12.0", - "symfony/process": "^7.2" + "phpunit/phpunit": "^12.5.33", + "symfony/process": "^7.4.17" }, "type": "library", "extra": { @@ -11922,15 +12123,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", + "type": "tidelift" } ], - "time": "2025-02-07T04:55:46+00:00" + "time": "2026-08-25T15:35:54+00:00" }, { "name": "sebastian/environment", @@ -12970,16 +13183,16 @@ }, { "name": "symfony/polyfill-php83", - "version": "v1.37.0", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149" + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149", - "reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/5ea99087fb99c273a9b9236ed4c31e78b16103c6", + "reference": "5ea99087fb99c273a9b9236ed4c31e78b16103c6", "shasum": "" }, "require": { @@ -13026,7 +13239,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.41.0" }, "funding": [ { @@ -13046,24 +13259,24 @@ "type": "tidelift" } ], - "time": "2026-04-10T17:25:58+00:00" + "time": "2026-07-01T12:47:55+00:00" }, { "name": "symfony/stopwatch", - "version": "v8.0.8", + "version": "v8.1.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3" + "reference": "21c07b026905d596e8379caeb115d87aa479499d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/85954ed72d5440ea4dc9a10b7e49e01df766ffa3", - "reference": "85954ed72d5440ea4dc9a10b7e49e01df766ffa3", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/21c07b026905d596e8379caeb115d87aa479499d", + "reference": "21c07b026905d596e8379caeb115d87aa479499d", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/service-contracts": "^2.5|^3" }, "type": "library", @@ -13092,7 +13305,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v8.0.8" + "source": "https://github.com/symfony/stopwatch/tree/v8.1.0" }, "funding": [ { @@ -13112,31 +13325,32 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-05-29T05:06:50+00:00" }, { "name": "symfony/yaml", - "version": "v8.0.8", + "version": "v8.1.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1" + "reference": "0b4aa53a67f9fece88c665f1a1dadcfd25d93fe5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/54174ab48c0c0f9e21512b304be17f8150ccf8f1", - "reference": "54174ab48c0c0f9e21512b304be17f8150ccf8f1", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0b4aa53a67f9fece88c665f1a1dadcfd25d93fe5", + "reference": "0b4aa53a67f9fece88c665f1a1dadcfd25d93fe5", "shasum": "" }, "require": { - "php": ">=8.4", + "php": ">=8.4.1", "symfony/polyfill-ctype": "^1.8" }, "conflict": { "symfony/console": "<7.4" }, "require-dev": { - "symfony/console": "^7.4|^8.0" + "symfony/console": "^7.4|^8.0", + "yaml/yaml-test-suite": "*" }, "bin": [ "Resources/bin/yaml-lint" @@ -13167,7 +13381,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v8.0.8" + "source": "https://github.com/symfony/yaml/tree/v8.1.6" }, "funding": [ { @@ -13187,7 +13401,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T15:14:47+00:00" + "time": "2026-08-30T01:03:44+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", diff --git a/config/custom-fields.php b/config/custom-fields.php index b353181f..8dbcb7cb 100644 --- a/config/custom-fields.php +++ b/config/custom-fields.php @@ -45,21 +45,53 @@ | Configure package features using the type-safe enum-based configurator. | This consolidates all feature settings into a single, organized system. | + | Every feature is listed below, on or off, with the reason for its default. + | A feature is on when it only adds a control to the field editor and is a + | no-op for fields that do not use it; it is off when turning it on would + | change how existing values are stored, validated, or displayed. + | */ 'features' => FeatureConfigurator::configure() ->enable( CustomFieldsFeature::FIELD_CONDITIONAL_VISIBILITY, CustomFieldsFeature::FIELD_ENCRYPTION, CustomFieldsFeature::FIELD_OPTION_COLORS, + CustomFieldsFeature::FIELD_DESCRIPTION, CustomFieldsFeature::UI_TABLE_COLUMNS, CustomFieldsFeature::UI_TOGGLEABLE_COLUMNS, CustomFieldsFeature::UI_TABLE_FILTERS, - CustomFieldsFeature::FIELD_DESCRIPTION, CustomFieldsFeature::UI_FIELD_WIDTH_CONTROL, CustomFieldsFeature::SYSTEM_MANAGEMENT_INTERFACE, CustomFieldsFeature::SYSTEM_SECTIONS, + + // Turned on at 4.0. Three change nothing about what you already store: a + // field with no rules validates as before, + CustomFieldsFeature::FIELD_VALIDATION_RULES, + // an unset position still renders the description below the input, + CustomFieldsFeature::FIELD_DESCRIPTION_POSITION, + // and a section with no conditions renders on every record. + CustomFieldsFeature::SECTION_CONDITIONAL_VISIBILITY, + // Sections stay full width too, with one exception: a preset migration that + // passed a width stored it even while this was off, and it now applies. + CustomFieldsFeature::UI_SECTION_WIDTH_CONTROL, + // New at 4.0: while off, the relationship migrations and the upgrade steps + // never run; a record field with a definition still reads, writes, and deletes its links. + CustomFieldsFeature::SYSTEM_RELATIONSHIPS, ) ->disable( + // Would take the code away from whoever creates the field, and codes are the + // identifier host code and imports address a field by. + CustomFieldsFeature::FIELD_CODE_AUTO_GENERATE, + // Turns a field's storage into a list of values; that is a data-shape decision. + CustomFieldsFeature::FIELD_MULTI_VALUE, + // Adds a uniqueness rule over values that already exist. + CustomFieldsFeature::FIELD_UNIQUE_VALUE, + // Offers the host's own model columns as condition sources; only the host knows + // which of its columns are safe to expose in the field editor. + CustomFieldsFeature::MODEL_ATTRIBUTE_CONDITIONS, + // Would hide existing custom-field columns from tables that show them today. + CustomFieldsFeature::UI_TOGGLEABLE_COLUMNS_HIDDEN_DEFAULT, + // Tenant isolation depends on the host's tenancy; see the multi-tenancy docs. CustomFieldsFeature::SYSTEM_MULTI_TENANCY, ), @@ -75,7 +107,10 @@ 'management' => [ 'slug' => 'custom-fields', 'navigation_sort' => -1, - 'navigation_group' => true, + + // Nest the management page under its own navigation group instead of top-level. + 'navigation_group_enabled' => true, + 'cluster' => null, // Width of the add/edit section modal. Accepts a Filament\Support\Enums\Width case or its @@ -84,6 +119,32 @@ 'section_modal_width' => null, ], + /* + |-------------------------------------------------------------------------- + | UI Flavor + |-------------------------------------------------------------------------- + | + | Five surfaces have no Filament primitive behind them, so each ships in two + | presentations of the same Livewire class: 'native' renders the view the surface + | shipped with before the 4.0 redesign, 'polished' renders the redesigned one. + | Only views fork, never the logic underneath, which is what keeps a second + | presentation cheap enough to carry. + | + | Polished is the default because it is the experience the package is designed + | around. Switch the whole panel with 'flavor', or name single surfaces in + | 'flavor_overrides'. The only accepted keys there are the five forked surfaces: + | relationship-configurator, record-chips, record-picker, type-picker, and + | attribute-table. Anything else throws rather than falling back silently. + | + */ + 'ui' => [ + 'flavor' => 'polished', + + 'flavor_overrides' => [ + // 'attribute-table' => 'native', + ], + ], + /* |-------------------------------------------------------------------------- | Field Settings @@ -116,7 +177,7 @@ | searchable_threshold controls when option-backed selects render a search | box. Set it to 0 to always show one, which is the pre-3.8 behavior. | - | record_lookup governs the record-select field's initial page and search. + | record governs the record-select field's initial page and search. | order_column null means the model's key, which is backed by the primary | key index and so costs no more than an unordered query. Naming a column | instead (for example 'updated_at' for most-recently-touched-first) is @@ -127,7 +188,7 @@ 'selects' => [ 'searchable_threshold' => 10, - 'record_lookup' => [ + 'record' => [ 'order_column' => null, 'order_direction' => 'desc', 'limit' => 50, @@ -135,14 +196,6 @@ ], ], - /* - |-------------------------------------------------------------------------- - | Database Configuration - |-------------------------------------------------------------------------- - | - | Configure database table names and migration paths. - | - */ /* |-------------------------------------------------------------------------- | Currency Configuration @@ -180,11 +233,18 @@ 'database' => [ 'migrations_path' => database_path('custom-fields'), + + // Key type of the tables added in 4.0: 'bigint', 'ulid', or 'uuid'. A ULID or UUID host + // sets it here instead of hand-editing them; the older tables keep the hand-edit path. + 'key_type' => 'bigint', + 'table_names' => [ 'custom_field_sections' => 'custom_field_sections', 'custom_fields' => 'custom_fields', 'custom_field_values' => 'custom_field_values', 'custom_field_options' => 'custom_field_options', + 'custom_field_relationships' => 'custom_field_relationships', + 'custom_field_links' => 'custom_field_links', ], 'column_names' => [ 'tenant_foreign_key' => 'tenant_id', diff --git a/config/data.php b/config/data.php index 554f166b..d8494f54 100644 --- a/config/data.php +++ b/config/data.php @@ -1,5 +1,7 @@ + */ +final class CustomFieldLinkFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = CustomFieldLink::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'relationship_id' => CustomFieldRelationship::factory(), + 'from_entity_type' => 'post', + 'from_entity_id' => $this->faker->randomNumber(), + 'to_entity_type' => 'user', + 'to_entity_id' => $this->faker->randomNumber(), + 'sort_order' => 0, + 'active_from' => Carbon::now(), + 'active_until' => null, + 'source' => CustomFieldLink::SOURCE_USER, + ]; + } +} diff --git a/database/factories/CustomFieldRelationshipFactory.php b/database/factories/CustomFieldRelationshipFactory.php new file mode 100644 index 00000000..aefbedb8 --- /dev/null +++ b/database/factories/CustomFieldRelationshipFactory.php @@ -0,0 +1,43 @@ + + */ +final class CustomFieldRelationshipFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = CustomFieldRelationship::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'code' => $this->faker->unique()->word(), + 'from_entity_type' => 'post', + 'to_entity_type' => 'user', + 'cardinality' => RelationshipCardinality::ManyToMany, + 'from_field_id' => null, + 'to_field_id' => null, + 'is_symmetric' => false, + 'created_at' => Carbon::now(), + 'updated_at' => Carbon::now(), + ]; + } +} diff --git a/database/factories/CustomFieldValueFactory.php b/database/factories/CustomFieldValueFactory.php index d0cc3bce..ffc0a7b0 100644 --- a/database/factories/CustomFieldValueFactory.php +++ b/database/factories/CustomFieldValueFactory.php @@ -1,5 +1,7 @@ index(['entity_id', 'custom_field_id'], 'custom_field_values_entity_id_custom_field_id_index'); }); } - - public function down(): void - { - Schema::dropIfExists(config('custom-fields.database.table_names.custom_field_values')); - 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)) { - Schema::dropIfExists(config('custom-fields.database.table_names.custom_field_sections')); - } - } }; diff --git a/database/migrations/create_relationship_definitions_table.php b/database/migrations/create_relationship_definitions_table.php new file mode 100644 index 00000000..86f52d75 --- /dev/null +++ b/database/migrations/create_relationship_definitions_table.php @@ -0,0 +1,62 @@ +nullable()->index(); + + $uniqueColumns[] = $tenantKey; + } + + $table->string('code'); + $table->string('from_entity_type'); + $table->string('to_entity_type'); + $table->string('cardinality'); + + // Slot keys follow the swapped CustomField model, not database.key_type: a host can + // run ULID custom fields while the tables added in 4.0 stay on bigint. + $customFields = config('custom-fields.database.table_names.custom_fields'); + + $table->foreignIdFor(CustomFields::customFieldModel(), 'from_field_id') + ->nullable() + ->unique() + ->constrained($customFields) + ->nullOnDelete(); + + $table->foreignIdFor(CustomFields::customFieldModel(), 'to_field_id') + ->nullable() + ->unique() + ->constrained($customFields) + ->nullOnDelete(); + + $table->boolean('is_symmetric')->default(false); + + $table->timestamps(); + + $table->unique($uniqueColumns); + }); + } +}; diff --git a/database/migrations/create_relationship_links_table.php b/database/migrations/create_relationship_links_table.php new file mode 100644 index 00000000..dc17554a --- /dev/null +++ b/database/migrations/create_relationship_links_table.php @@ -0,0 +1,64 @@ +nullable() + ->index(); + } + + KeyType::foreign($table, 'relationship_id') + ->constrained(config('custom-fields.database.table_names.custom_field_relationships')) + ->cascadeOnDelete(); + + KeyType::morphs($table, 'from_entity'); + KeyType::morphs($table, 'to_entity'); + + $table->unsignedInteger('sort_order')->nullable(); + + $table->dateTime('active_from'); + $table->dateTime('active_until')->nullable(); + + KeyType::morphs($table, 'created_by', nullable: true); + + $table->string('source', 32)->default('user'); + $table->float('confidence')->nullable(); + + $table->index(['relationship_id', 'from_entity_id', 'active_until'], 'cf_links_from_idx'); + $table->index(['relationship_id', 'to_entity_id', 'active_until'], 'cf_links_to_idx'); + }); + + // The only driver switch in this package: a partial unique index is the duplicate-edge + // wall, and the MySQL family has none. There the writer alone enforces it (spec 1.2). + if (in_array(DB::getDriverName(), ['pgsql', 'sqlite'], true)) { + DB::statement(sprintf( + 'CREATE UNIQUE INDEX %s ON %s (relationship_id, from_entity_type, from_entity_id, to_entity_type, to_entity_id) WHERE active_until IS NULL', + CustomFieldLink::ACTIVE_EDGE_INDEX, + Schema::getConnection()->getSchemaGrammar()->wrapTable($links), + )); + } + } +}; diff --git a/database/migrations/drop_custom_fields_lookup_type.php b/database/migrations/drop_custom_fields_lookup_type.php new file mode 100644 index 00000000..1c69f35b --- /dev/null +++ b/database/migrations/drop_custom_fields_lookup_type.php @@ -0,0 +1,58 @@ +assertRecordLinksAreMigrated(); + + Schema::table($table, function (Blueprint $blueprint): void { + $blueprint->dropColumn('lookup_type'); + }); + } + + private function assertRecordLinksAreMigrated(): void + { + $codes = app(UnmigratedRecordFields::class)->withoutDefinition(); + + if ($codes === []) { + return; + } + + throw new RuntimeException(sprintf( + 'Cannot drop custom_fields.lookup_type: %s point at another entity through that column with no relationship definition to hold it. Run `php artisan custom-fields:upgrade` (step %s) first, then migrate again.', + implode(', ', $codes), + UpgradeCommand::STEP_MIGRATE_RECORD_LINKS, + )); + } +}; diff --git a/database/migrations/relax_custom_fields_unique_key.php b/database/migrations/relax_custom_fields_unique_key.php index 37c9b5a9..119dbda5 100644 --- a/database/migrations/relax_custom_fields_unique_key.php +++ b/database/migrations/relax_custom_fields_unique_key.php @@ -4,7 +4,6 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\FeatureSystem\FeatureManager; @@ -42,58 +41,8 @@ public function up(): void ); } - public function down(): void - { - $this->assertNoDuplicatesUnderNarrowKey(); - - $this->swapUniqueKey( - from: $this->wideColumns(), - fromIndexName: $this->wideIndexName(), - to: $this->narrowColumns(), - toIndexName: null, - ); - } - - /** - * MySQL runs each ALTER TABLE as its own auto-committing DDL statement, so dropping - * the wide key and adding the narrow one are not transactional together. If rows exist - * that share (code, entity_type[, tenant]) across different sections — exactly the - * shape the wide key exists to allow — the DROP succeeds and the subsequent ADD fails - * on the duplicate, leaving the table with neither unique key. Check first and abort - * before touching anything. - */ - private function assertNoDuplicatesUnderNarrowKey(): void - { - $table = config('custom-fields.database.table_names.custom_fields'); - - if (! Schema::hasColumn($table, 'custom_field_section_id')) { - return; - } - - $columns = $this->narrowColumns(); - - $duplicateCodes = DB::table($table) - ->select($columns) - ->groupBy($columns) - ->havingRaw('count(*) > 1') - ->pluck('code'); - - if ($duplicateCodes->isEmpty()) { - return; - } - - throw new RuntimeException(sprintf( - 'Cannot roll back the custom_fields unique key: %d code(s) — including "%s" — are shared by more than one row for the same (%s), only differing by custom_field_section_id. onlySections() allows this under the wide key, but the narrow key being restored cannot. Resolve or remove the duplicate rows before rolling back this migration.', - $duplicateCodes->count(), - $duplicateCodes->first(), - implode(', ', $columns) - )); - } - /** - * Drops $from's unique key if present and adds $to's if absent. Shared by both - * directions: up() widens (code, entity_type[, tenant]) to also include - * custom_field_section_id; down() narrows it back to the original key. + * Drops $from's unique key if present and adds $to's if absent. * * @param array $from * @param array $to diff --git a/docs/content/1.getting-started/1.installation.md b/docs/content/1.getting-started/1.installation.md index a4ed1f49..3479003d 100644 --- a/docs/content/1.getting-started/1.installation.md +++ b/docs/content/1.getting-started/1.installation.md @@ -88,7 +88,7 @@ php artisan vendor:publish --tag="custom-fields-views" ## Picking Up New Migrations After an Upgrade Package migrations are not run automatically by `php artisan migrate` after a version -bump — they're only copied into your app the first time you install, or when you +bump. They are only copied into your app the first time you install, or when you explicitly republish them. If a release adds or changes a migration, republish and run it: diff --git a/docs/content/1.getting-started/3.upgrade-guide.md b/docs/content/1.getting-started/3.upgrade-guide.md index 75dbb6d3..4d452820 100644 --- a/docs/content/1.getting-started/3.upgrade-guide.md +++ b/docs/content/1.getting-started/3.upgrade-guide.md @@ -1,55 +1,421 @@ --- title: Upgrade Guide -description: Upgrade Custom Fields from v2 to v3 +description: Upgrade Custom Fields to 4.0 navigation: icon: i-lucide-arrow-up-right --- -## Requirements +## Upgrading to 4.0 -Custom Fields v3 requires: +4.0 moves record links out of `custom_field_values.json_value` and into a relationship +ledger of their own, adds two field types beside the Record field you already have, and +closes every class that is not a documented extension point. This page is the whole +upgrade: what to run and in what order, what your users see, what breaks in your code, and +a checklist to work through. + +### Requirements - **PHP** 8.3+ - **Laravel** 12+ - **Filament** 5.0+ +- The latest 3.x release of Custom Fields, installed and migrated + +Upgrade to the latest 3.x first. The v1-era upgrade script and the 2.x-to-3.x data-migration +steps are gone, so `php artisan custom-fields:upgrade` carries only the steps 4.0 needs and +cannot catch a host up from further back. If you are still on 2.x, work through +[Upgrading to 3.0](#upgrading-to-30) first. -## Quick Upgrade +### ULID and UUID applications: set the key type before you migrate + +`database.key_type` shapes the keys of the two tables 4.0 adds. It is read at runtime, so +set it before the migrations run and a ULID or UUID application never hand-edits them: + +```php +// config/custom-fields.php +'database' => [ + // 'bigint', 'ulid', or 'uuid' + 'key_type' => 'ulid', + // ... +], +``` + +`tenant_id`, `relationship_id`, and every polymorphic end follow this setting. The two +relationship slot columns (`from_field_id`, `to_field_id`) follow your `CustomField` model +instead, because they point at it. The four tables that predate 4.0 keep the hand-edit path +they have always had. Leaving `key_type` at `bigint` on a ULID host means inserts fail with +a key-type mismatch, so set it in the same change that pulls 4.0 in. + +### The upgrade, in order + +`custom-fields:upgrade` needs the two new tables to exist before it can write a definition, +and the migration that drops `custom_fields.lookup_type` refuses to run before that command +has read the column. So the tables come first, the command second, the drop last. ```bash -composer require relaticle/custom-fields:"^3.0" -W -php artisan migrate -php artisan custom-fields:upgrade +# 1. Pull the release in and publish the new migrations. +composer require relaticle/custom-fields:"^4.0" -W +php artisan vendor:publish --tag="custom-fields-migrations" + +# 2. Create the two relationship tables, and nothing else yet. +php artisan migrate --path=database/migrations/_create_relationship_definitions_table.php +php artisan migrate --path=database/migrations/_create_relationship_links_table.php + +# 3. Give every record field a definition and turn its stored ids into links. +php artisan custom-fields:upgrade --dry-run +php artisan custom-fields:upgrade --force + +# 4. Now the drop has what it needs. +php artisan migrate --force ``` -The upgrade command automatically handles data migrations for phone fields, email fields, and lookup fields. +Step 2 names the two published files because a plain `migrate` would reach +`drop_custom_fields_lookup_type` in the same run and **exit non-zero**: that migration +throws rather than dropping a column your record fields still depend on. Nothing is lost +when it happens, the migrations before it are recorded and re-running `migrate` after the +upgrade command picks up where it stopped, but a deploy pipeline running +`php artisan migrate --force` reports the release as failed, and a red deploy is a poor way +to find this out. Run steps 1 to 3 against the running 3.x-schema application, then let the +deploy migrate normally. + +If you would rather not name files, run `php artisan migrate`, expect it to fail at the +drop, then run `custom-fields:upgrade` and `migrate` again. The outcome is identical. A +fresh install has no record fields to migrate and runs straight through either way. + +The exception names the fields it stopped for: -### Command Options +> Cannot drop custom_fields.lookup_type: `` point at another entity through that +> column with no relationship definition to hold it. Run `php artisan custom-fields:upgrade` +> (step migrate-record-links) first, then migrate again. -| Option | Description | -|--------|-------------| -| `--dry-run` | Show what would be migrated without making changes | -| `--force` | Run without confirmation prompts | -| `--skip=` | Skip specific steps (comma-separated) | +It counts a record field with a target on the retiring column and no definition, whether or +not anyone has stored a value in it. A record field that never had a target does not block +the drop. -**Skippable steps**: `lookup-fields`, `email-format`, `phone-format`, `validate-schema`, `clear-caches` +#### What the upgrade command runs + +`custom-fields:upgrade` runs four steps in order and stops at the first failure, because +every later step reads what an earlier one wrote: + +| Step | Runs | Does | +|---|---|---| +| `validate-schema` | Always | Checks every required table and column, including the two relationship tables when `SYSTEM_RELATIONSHIPS` is on, and reports record fields whose links are still in `json_value`. | +| `migrate-record-links` | Always | Gives every record field a relationship definition and turns each stored `json_value` array into edges. | +| `purge-record-values` | Only with `--purge` | Deletes the value rows the previous step copied from. | +| `clear-caches` | Always | Clears the package's cached field and entity metadata. | + +`--dry-run` reports without writing. `--force` skips the confirmation prompt, which is what +a deploy script wants. `--skip=` takes a comma-separated list of the step keys above. + +`migrate-record-links` creates a many-to-many definition where the field allowed multiple +values and a many-to-one definition where it did not, and stamps every edge with +`source = migration`, keeping the order the stored array held. Reruns create nothing twice. +The field keeps its `record` type, and the command's log says so, so a host reading it sees +no rename. + +#### Verify, then purge + +The migration keeps the old value rows, so both stores can be compared and a rollback stays +trivial at every point. Read your record fields back through the panel and through the API, +in both directions (set a value and clear one), and only then delete the old rows: ```bash -# Preview changes without applying them -php artisan custom-fields:upgrade --dry-run +php artisan custom-fields:upgrade --purge --force +``` -# Run in CI/CD without prompts -php artisan custom-fields:upgrade --force +The purge refuses while any record field still stores links in `json_value` with no +definition, and `validate-schema` fails on that same state whenever the current run will not +fix it, either because the migration step was skipped or because the two tables are not +migrated yet. + +### What your users see + +#### Three field types where there was one + +| Type | Key | What it is | +|---|---|---| +| Record | `record` | The one-way link 3.x already had. Unchanged for users. | +| Relationship | `relationship` | New. A link both entities show, with a field on each end. | +| Status | `status` | New. A single choice whose options carry a workflow category. | + +**Record is unchanged.** The field editor asks the same two questions, the entity it points +at and whether it holds more than one record, and the form still renders a searchable +select. What it gains is the ledger underneath: record columns are now sortable and +searchable, a force-deleted record takes its links with it, and the single-value filter bug +is gone with the storage that hosted it. What it loses is `max_values`, which capped a count +the cardinality now owns. + +**Relationship is new.** It configures the cardinality as a sentence between two entity +cards, names the field that appears on the other entity, and can be symmetric when the link +reads the same in both directions. It renders records as chips with avatars and a page to +open, its picker offers to create a record it could not find, and where an end holds a +single record it confirms a move before taking that record from whoever holds it. Nothing +migrates into it: a record field stays a record field, and turning one into a relationship +means adding a second slot to its definition, which 4.0 does not offer. + +**Status is new.** It is a single-choice field whose options each carry a category from a +closed set (`unstarted`, `started`, `completed`, `cancelled`), so reports ask for the +completed options instead of matching the label `Done`. It renders the same input, column, +entry and filter a Select does, and it stores a single option id the same way. Select itself +is unchanged, and no existing field changes type. See +[Option Categories](/essentials/option-categories) and +[Relationships](/essentials/relationships). + +An application that lists its field types in `field_type_configuration->enabled()` gets the +two new types only after adding `relationship` and `status` to that list. An empty list +still means all of them. + +#### Pasting a list of options + +The options editor gained a paste action: one option name per line, up to 100 at a time, +trimmed and deduplicated case-insensitively against the names already there. A 200-value +vocabulary used to be 200 clicks. See +[Field Types](/essentials/field-types#user-defined-options-default). + +#### Emptying a field clears it + +A Filament form now dehydrates a field the user emptied, so clearing a multi-value field +through the panel clears it. 3.x withheld the empty state and kept the stored value. No host +action is needed. + +A clear travels only on a field the server can prove the form was showing. Where a field's +visibility rests on a condition the server cannot reproduce, a model-attribute source among +them, an emptied field keeps its stored value instead of clearing, which is the fail-safe +direction for a destructive write. + +### What breaks in your code + +#### Component interface signatures + +`make()` on `FormComponentInterface`, `InfolistComponentInterface`, `TableColumnInterface`, +and `TableFilterInterface` gains a `?Illuminate\Database\Eloquent\Model $record = null` +parameter. `TableFilterInterface::make()` also gains a `?string $through = null` parameter +after it, which is what [through relations](/essentials/through-relations) pass down. + +Third-party components implementing one of these interfaces directly must add the new +parameters to their `make()` signature, and so must components extending +`AbstractInfolistEntry`, `AbstractTableColumn`, or `AbstractTableFilter`. Components that +extend `AbstractFormComponent` inherit the new signature automatically: that base implements +`make()` itself, so subclasses only implement `create()`. + +#### Contract renames and interface collapse + +| 3.x | 4.0 | +|---|---| +| `Contracts\ValueResolvers` | `Contracts\ValueResolverInterface` | +| `Contracts\ValidationCapability` | `Contracts\ValidationCapabilityInterface` | +| `Contracts\CustomsFieldsMigrators` | Removed. Type-hint `Filament\Integration\Migrations\CustomFieldsMigrator`. | +| `Contracts\EntityManagerInterface` | Removed. Type-hint `EntitySystem\EntityManager`. | +| `Contracts\EntityConfigurationInterface` | Removed. Type-hint `EntitySystem\EntityConfigurator`. | + +The three removed contracts each had exactly one implementation and were never documented +extension points. `CustomFieldsMigrator` is now `final` as well (the other two already +were), so a subclass of it no longer compiles. + +#### Field type capability rename + +`FieldSchema::requiresLookupType()` is now `requiresRelationship()`, and the flag it sets on +`FieldTypeData` is `$requiresRelationship`. A record field's target no longer lives on the +field row: it is one end of a relationship definition. Custom field types that called the +old method must rename the call. + +`CustomFieldData` loses its `$lookupType` property for the same reason. A preset that passed +it as a constructor argument moves to `CustomFieldsMigrator::lookupType()`, which now +creates a one-way relationship definition around the field it writes. See +[Preset Custom Fields](/essentials/preset-custom-fields). + +Two capabilities are new beside it: `supportsPairing()`, which the Relationship type sets +and which decides whether a field gets the configurator and the chips, and +`carriesOptionCategories()`, which the Status type sets and which decides whether the +options editor asks for a category. Read the capability, never the type key. + +#### Config key renames + +`selects.record_lookup` is now `selects.record`, matching the record vocabulary the rest of +4.0 uses. Rename the block in your published config; the old key is not read. + +`management.navigation_group` was a dead key: the package always read +`management.navigation_group_enabled` (default `true`) instead, so setting +`navigation_group` had no effect. Rename it in your published config. If you had set it to +`false` expecting the management page's navigation group to be disabled, that will now +actually take effect. + +#### A new `ui` block for the flavor registry + +Five surfaces have no Filament primitive behind them, so each ships in two presentations of +the same Livewire class. `ui.flavor` is `polished` (the redesigned views, the default) or +`native` (the views those surfaces shipped with before the redesign), and +`ui.flavor_overrides` switches a single surface. It accepts only five keys: +`relationship-configurator`, `record-chips`, `record-picker`, `type-picker`, and +`attribute-table`. + +```php +'ui' => [ + 'flavor' => 'polished', -# Skip specific steps -php artisan custom-fields:upgrade --skip=clear-caches -php artisan custom-fields:upgrade --skip=email-format,phone-format + 'flavor_overrides' => [ + // 'attribute-table' => 'native', + ], +], ``` +Only views fork, never the logic underneath. An unknown flavor or an unknown override key is +rejected at boot rather than falling back silently, so a typo fails on the first request +instead of quietly reverting a surface. In a console process it is reported to your log +instead of thrown, so a bad value never stops `migrate` from running. A config published +before 4.0 has no `ui` block at all and gets the defaults. See +[Configuration](/essentials/configuration#ui-flavor). + +#### Feature flag defaults + +Every feature flag is now listed explicitly in `config/custom-fields.php`, on or off, with +the reason for its default. Four flags that were off in 3.x ship on: + +| Flag | 3.x | 4.0 | Why | +|---|---|---|---| +| `FIELD_VALIDATION_RULES` | Off | On | Adds the per-field validation editor; a field with no rules configured validates exactly as before. | +| `FIELD_DESCRIPTION_POSITION` | Off | On | Adds an above/below choice to the description that `FIELD_DESCRIPTION` already shows; unset still renders below. | +| `SECTION_CONDITIONAL_VISIBILITY` | Off | On | The section-level half of conditional visibility, which was already on for fields; a section with no conditions renders as before. It also widens the section modal so the conditions row fits. | +| `UI_SECTION_WIDTH_CONTROL` | Off | On | Section width, requested in #181. Sections default to 100%, so nothing moves, with one exception: a preset migration that passed a `width` to `CustomFieldSectionData` stored it even with the flag off, and that stored width now applies. | + +`SYSTEM_RELATIONSHIPS` is new and ships on. `FIELD_CODE_AUTO_GENERATE`, +`FIELD_MULTI_VALUE`, `FIELD_UNIQUE_VALUE`, `MODEL_ATTRIBUTE_CONDITIONS`, +`UI_TOGGLEABLE_COLUMNS_HIDDEN_DEFAULT`, and `SYSTEM_MULTI_TENANCY` stay off: each one +changes how existing values are stored, validated, or displayed, so it is the application's +call, not the package's. + +**An unlisted flag now takes the package default.** Your own `features` block still wins for +every flag it names, but a flag it does not name reads the shipped default instead of +reading as off. A config published on 3.x therefore inherits the four flips above and +`SYSTEM_RELATIONSHIPS` with them. Compare your block against the shipped file when you want +the 4.0 answer for a flag you did name. + +A `FIELD_OPTION_CATEGORIES` flag was added and removed inside 4.0, before the release, so +there is nothing to migrate unless you published a config during a 4.0 pre-release that +names it. If yours does, delete that line: the enum case is gone, and +`FeatureConfigurator::enable(CustomFieldsFeature::FIELD_OPTION_CATEGORIES)` now fatals on an +undefined constant. Withhold the `status` type through `field_type_configuration` if you do +not want categorised options at all. + +#### What `SYSTEM_RELATIONSHIPS` gates + +It gates the two migrations and the upgrade command's own steps, and nothing else. With it +off the tables are never created, `custom_fields.lookup_type` is never dropped, and +`custom-fields:upgrade` has nothing to do, so an application with no record fields can opt +out entirely. + +It does not gate a record field's own reads, writes, or deletes. A field with a definition +keeps reading from and writing to its links regardless of the flag, because no definition +can exist without the tables it lives in, and deleting the field still unpairs its slot the +same way. Turning the flag off after migrating does not change how the record fields you +already migrated behave. + +#### `lookup_type` is gone, and cardinality owns multiplicity + +`custom_fields.lookup_type` is dropped by `drop_custom_fields_lookup_type`. A record field's +target is one end of its relationship definition, so anything that read the column reads the +definition instead: `CustomField::targetEntityType()` answers from there, and +`relationshipDefinitionOrFail()` is what the record column and filter call, because after +the drop a record field without a definition cannot exist. + +`allow_multiple` and `max_values` are ignored for record fields, and the field editor no +longer offers `max_values` there. How many records each end holds is the relationship's +cardinality: many-to-many where the field allowed multiple values, many-to-one where it did +not. The allow-multiple toggle still asks the question, it just writes a cardinality instead +of a setting. Narrowing it later keeps the first record each side is linked to and asks +before closing the rest. + +A record field whose definition is missing altogether renders nothing at all: the column, +the filter, the entry, and the form field take themselves out of the page, and the exception +is reported to your log once per request, naming the field. That is a broken field, not a +broken page, and `custom-fields:upgrade` is what fixes it. + +#### Partial payloads change meaning + +Once a record field has a relationship definition, a key that is absent from a +`custom_fields` payload leaves its links exactly as they are. The legacy value path read an +absent key as a clear, and every other field type still does. Clearing a relationship is now +explicit: send `[]` or `null`. + +Anything that writes a partial payload, a REST `PATCH`, an AI or chat action, an import that +maps only some columns, therefore changes behaviour on the day the migration step gives a +field its definition. Review those writers before you run it. + +#### Taking a record from its holder + +Where the far end of a relationship holds a single record, a payload that would take that +record from another one is rejected, naming the record that holds it. Confirm the +replacement per field by sending `['ids' => [...], 'replace' => true]` where the id list +goes. The plain list stays the default and stays unconfirmed. More than one id for a single +end is rejected as well. + +#### Host models that override `save()` + +The trait now defines `save(array $options = [])` so a record and its custom-field writes +land in one transaction. A host model that defines its own `save()` bypasses it silently and +loses that rollback, so call `parent::save()` from yours. + +#### Classes are final + +Every class under `src/` is now `final` except the documented extension points, so +inheritance is no longer a supported way to change package internals. The seams that stay +open are listed on [Extending](/essentials/extending): `BaseFieldType` and the shipped +field-type definitions, the `Abstract*` component bases, `CustomFieldsMigration`, the six +swappable models and their query builder, the management page, the contracts, and the +traits. + +Everything else is closed: services, factories, resolvers, builders, data objects, +exceptions, observers, scopes, middleware, facades, the management form schemas, and the +Livewire components. If you subclassed one of those, it no longer compiles. Move to the seam +that covers your case, or open an issue for a new seam; the answer to an extension request +is a documented seam, never an un-finalized internal. + +### MySQL and the duplicate-edge guard + +Duplicate open edges are blocked by a partial unique index, which PostgreSQL and SQLite +support and the MySQL family does not. The link writer enforces the same rule on every +driver inside its transaction, so nothing is missing functionally. A MySQL host that wants +the database-level guard as well can add it with a generated column: + +```sql +ALTER TABLE custom_field_links + ADD COLUMN active_edge_key VARCHAR(512) + GENERATED ALWAYS AS ( + IF(active_until IS NULL, + CONCAT_WS('|', relationship_id, from_entity_type, from_entity_id, + to_entity_type, to_entity_id), + NULL) + ) STORED, + ADD UNIQUE INDEX cf_links_active_edge_unique (active_edge_key); +``` + +The column is NULL for a closed edge, and MySQL treats NULLs in a unique index as distinct, +so history never collides with the edges that are open. + +### 4.0 Checklist + +- [ ] Back up your database. +- [ ] Verify PHP 8.3+, Laravel 12+, Filament 5+, and the latest 3.x release installed. +- [ ] Review every writer that sends a partial `custom_fields` payload: an absent record-field key stops meaning "clear". +- [ ] Set `database.key_type` if your application uses ULID or UUID keys. +- [ ] Rename `selects.record_lookup` to `selects.record` and `management.navigation_group` to `management.navigation_group_enabled` in your published config. +- [ ] Compare your `features` block against the shipped file: unlisted flags now take the package default. +- [ ] Update `make()` signatures on any component implementing the four component interfaces. +- [ ] Rename `requiresLookupType()` to `requiresRelationship()` on custom field types, and move `lookupType` off `CustomFieldData`. +- [ ] Update the renamed contracts and drop the three removed ones. +- [ ] Replace any subclass of a now-final internal with a documented seam. +- [ ] Call `parent::save()` from any host model that overrides `save()`. +- [ ] `composer require relaticle/custom-fields:"^4.0" -W` and publish the migrations. +- [ ] Create the two relationship tables, run `custom-fields:upgrade --dry-run`, then `custom-fields:upgrade`. +- [ ] Run `migrate` to drop `lookup_type`. +- [ ] Read record fields back through the panel and the API, setting a value and clearing one. +- [ ] Run `custom-fields:upgrade --purge` once those reads check out. +- [ ] On MySQL, add the generated-column unique index if you want the database-level duplicate-edge guard. +- [ ] Add `relationship` and `status` to `field_type_configuration->enabled()` if you list your types explicitly. + ## Picking Up New Migrations -Package migrations are not run automatically by `php artisan migrate` after a version -bump — they're only copied into your app on first install, or when you explicitly -republish them: +Package migrations are not run automatically by `php artisan migrate` after a version bump. +They are only copied into your app on first install, or when you explicitly republish them: ```bash php artisan vendor:publish --tag="custom-fields-migrations" @@ -58,41 +424,62 @@ php artisan migrate For example, a release may relax the `custom_fields` unique key from `(code, entity_type[, tenant])` to `(code, entity_type[, tenant], custom_field_section_id)` -so a field code can be reused across different sections — the shape `onlySections()` (see +so a field code can be reused across different sections, the shape `onlySections()` (see [Builder Scoping](/essentials/builder-scoping)) relies on. Existing installs need the republish-and-migrate step above to pick that change up; it is not applied automatically. -**Before rolling that migration back**, resolve any rows that ended up sharing a code -across sections. The migration checks for this first and aborts with a clear error rather -than dropping the wide key and then failing to recreate the narrow one, which would leave -the table with no unique key at all. +Package migrations are up-only and ship without a `down()` method; there is no supported +way to roll one back. If you need to reverse a widened unique key by hand, resolve any +rows that ended up sharing a code across sections first, then drop the wide index and +recreate the narrow one yourself. **NULL is not unique-constrained.** `custom_field_section_id` is nullable, and both MySQL -and Postgres treat `NULL` as distinct from every other value in a unique index — including +and Postgres treat `NULL` as distinct from every other value in a unique index, including one that includes it. So after this migration, two sectionless fields (`custom_field_section_id IS NULL`) can still share a code for the same entity type, a collision the narrow key used to prevent. There is no schema-level fix for this; keep sectionless codes unique at the application layer if you rely on that guarantee. -## Breaking Changes +## Upgrading to 3.0 + +### Requirements + +Custom Fields v3 requires: + +- **PHP** 8.3+ +- **Laravel** 12+ +- **Filament** 5.0+ + +### Quick Upgrade -### High Impact +```bash +composer require relaticle/custom-fields:"^3.0" -W +php artisan migrate +php artisan custom-fields:upgrade +``` -#### Filament 5 Required +The v3 upgrade command handled data migrations for phone fields, email fields, and lookup +fields automatically. + +### Breaking Changes + +#### High Impact + +##### Filament 5 Required Custom Fields v3 requires Filament 5. If you're still on Filament 4, upgrade Filament first following the [Filament upgrade guide](https://filamentphp.com/docs/5.x/upgrade-guide). -#### Lookup Fields Removed from Non-Record Types +##### Lookup Fields Removed from Non-Record Types -The `lookup_type` setting has been removed from field types that don't support entity lookups. The upgrade command migrates affected fields automatically. +The `lookup_type` setting was removed from field types that don't support entity lookups. The v3 upgrade command migrated affected fields automatically. **Affected field types**: Text, Textarea, Number, Date, DateTime, Email, Phone, and other non-relational types. -If you were using `lookup_type` on these fields, they will be converted to standard fields. +If you were using `lookup_type` on these fields, they were converted to standard fields. -### Medium Impact +#### Medium Impact -#### Phone Field Format Changed +##### Phone Field Format Changed Phone fields now store values as JSON arrays of E.164 strings (supporting multiple phone numbers): @@ -104,9 +491,9 @@ Phone fields now store values as JSON arrays of E.164 strings (supporting multip ["+11234567890"] ``` -The upgrade command migrates existing phone values automatically. +The v3 upgrade command migrated existing phone values automatically. -#### Email Field Format Changed +##### Email Field Format Changed Email fields now support multiple values stored as JSON: @@ -118,64 +505,15 @@ Email fields now support multiple values stored as JSON: ["user@example.com"] ``` -The upgrade command migrates existing email values automatically. - -### Low Impact - -#### New Phone Validation Package - -v3 adds `propaganistas/laravel-phone` for phone validation. This is installed automatically with composer. - -## Manual Upgrade Steps - -If you prefer manual control over the upgrade process: - -### 1. Update Dependencies - -```bash -composer require relaticle/custom-fields:"^3.0" -W -``` - -### 2. Run Migrations - -```bash -php artisan migrate -``` - -### 3. Run Upgrade Command +The v3 upgrade command migrated existing email values automatically. -The upgrade command performs these steps: +#### Low Impact -1. **Migrate Lookup Fields** - Removes lookup settings from non-record fields -2. **Migrate Email Format** - Converts email strings to JSON arrays -3. **Migrate Phone Format** - Converts phone strings to structured JSON -4. **Validate Schema** - Checks database integrity -5. **Clear Caches** - Clears all relevant caches +##### New Phone Validation Package -```bash -php artisan custom-fields:upgrade -``` - -You can skip specific steps if needed: +v3 added `propaganistas/laravel-phone` for phone validation. This is installed automatically with composer. -```bash -# Run everything except cache clearing -php artisan custom-fields:upgrade --skip=clear-caches - -# Run only lookup field migration -php artisan custom-fields:upgrade --skip=email-format,phone-format,validate-schema,clear-caches -``` - -### 4. Clear Caches - -```bash -php artisan cache:clear -php artisan config:clear -php artisan view:clear -php artisan filament:cache-components -``` - -## Troubleshooting +### Troubleshooting ::accordion :::accordion-item{label="Phone fields showing raw JSON"} @@ -185,24 +523,9 @@ php artisan filament:cache-components php artisan filament:cache-components ``` ::: - - :::accordion-item{label="Upgrade command fails on phone migration"} - Check for invalid phone numbers in your database: - ```sql - SELECT * FROM custom_field_values - WHERE custom_field_id IN ( - SELECT id FROM custom_fields WHERE type = 'phone' - ) AND text_value IS NOT NULL; - ``` - Invalid numbers are preserved as-is with a default country code. - ::: - - :::accordion-item{label="Missing lookup_type errors"} - If you have custom code referencing `lookup_type` on non-record fields, remove those references. Only the `record` field type supports lookups in v3. - ::: :: -## Migration Checklist +### Migration Checklist - [ ] Backup your database - [ ] Verify PHP 8.3+, Laravel 12+, Filament 5+ requirements diff --git a/docs/content/2.essentials/1.configuration.md b/docs/content/2.essentials/1.configuration.md index ebc2d53c..d60a5354 100644 --- a/docs/content/2.essentials/1.configuration.md +++ b/docs/content/2.essentials/1.configuration.md @@ -128,18 +128,18 @@ Configure the custom fields management page: ```php 'management' => [ - 'slug' => 'custom-fields', // URL slug - 'navigation_sort' => -1, // Navigation sort order - 'navigation_group' => true, // Group in navigation - 'cluster' => null, // Optional cluster assignment + 'slug' => 'custom-fields', // URL slug + 'navigation_sort' => -1, // Navigation sort order + 'navigation_group_enabled' => true, // Nest under its own navigation group + 'cluster' => null, // Optional cluster assignment ], ``` #### Replacing the Management Page The config above covers the common cases. Filament reads some things off the page -class itself rather than from config — sub-navigation, breadcrumbs, header actions — -so when you need one of those, register your own page instead: +class itself rather than from config, among them sub-navigation, breadcrumbs and header +actions, so when you need one of those, register your own page instead: ```php use App\Filament\Pages\Settings\CustomFields; @@ -172,9 +172,55 @@ class CustomFields extends CustomFieldsManagementPage ``` The page must extend `CustomFieldsManagementPage`, and it replaces the packaged page -rather than sitting alongside it — only one management page is registered on the panel, +rather than sitting alongside it. Only one management page is registered on the panel, so there is no second route to the same screen. +### UI Flavor + +Five surfaces have no Filament primitive behind them, so the package ships two presentations +of each, over the same Livewire class: + +```php +'ui' => [ + 'flavor' => 'polished', + + 'flavor_overrides' => [ + // 'attribute-table' => 'native', + ], +], +``` + +| Flavor | Renders | +|---|---| +| `polished` (default) | The redesigned views, `custom-fields::flavors.polished.`. | +| `native` | The view the surface shipped with before the redesign. | + +`flavor` sets the presentation for the whole panel. `flavor_overrides` changes a single +surface, and accepts only these five keys: + +| Key | Surface | +|---|---| +| `relationship-configurator` | The relationship creation and editing flow. | +| `record-chips` | Linked records shown on a record, for a relationship field. | +| `record-picker` | The search-and-select panel that links them, for a relationship field. | +| `type-picker` | The field-type grid in the create-field flow. | +| `attribute-table` | The field-management table. | + +The three relationship keys reach the paired `relationship` type only. A `record` field is +the one-way link it has always been, and renders the same select, column and entry in both +flavors. + +No other screen forks: everywhere else both flavors render the same view. An unknown flavor, +or a key that is not one of the five, is rejected when the package boots rather than falling +back silently, so a typo in a published config fails on the first request instead of quietly +reverting a surface. In a console process it is reported to your log instead of thrown, so a +bad value never stops `migrate` or a queue worker from running. + +Only views fork. The services, validation, and writes behind each surface are shared, so a +flavor decides what a screen looks like and never what it does. That rule is what bounds the +cost of carrying a second presentation, an accepted 15 to 20 percent on UI work; behaviour +that would need forked logic belongs behind one view, not two. + ### Select Behavior Controls when option-backed selects render a search box, and how the record-select field @@ -184,7 +230,7 @@ orders and pages its lookups: 'selects' => [ 'searchable_threshold' => 10, - 'record_lookup' => [ + 'record' => [ 'order_column' => null, 'order_direction' => 'desc', 'limit' => 50, @@ -196,10 +242,10 @@ orders and pages its lookups: | Key | Default | Effect | |---|---|---| | `searchable_threshold` | `10` | Select and multi-select fields render a search box only when they have more options than this. Set it to `0` to always render one. | -| `record_lookup.order_column` | `null` | Column the record-select orders its initial page and search results by. `null` means the model's key, which is backed by the primary key index. Name a real column to override. | -| `record_lookup.order_direction` | `'desc'` | Direction for that column. The model key is always applied after it, so rows sharing a value keep a fixed order. | -| `record_lookup.limit` | `50` | Rows fetched for the initial page and for each search. | -| `record_lookup.min_search_length` | `2` | Characters required before a filtered lookup query is issued. Below it, the field shows the unfiltered first page. Both the server and the field's JavaScript read this value. | +| `record.order_column` | `null` | Column the record-select orders its initial page and search results by. `null` means the model's key, which is backed by the primary key index. Name a real column to override. | +| `record.order_direction` | `'desc'` | Direction for that column. The model key is always applied after it, so rows sharing a value keep a fixed order. | +| `record.limit` | `50` | Rows fetched for the initial page and for each search. | +| `record.min_search_length` | `2` | Characters required before a filtered lookup query is issued. Below it, the field shows the unfiltered first page. Both the server and the field's JavaScript read this value. | ::alert{type="info"} Before 3.8 every select rendered a search box, including a three-option status field. Set @@ -222,11 +268,17 @@ Customize table names and paths: ```php 'database' => [ 'migrations_path' => database_path('custom-fields'), + + // 'bigint', 'ulid', or 'uuid' + 'key_type' => 'bigint', + 'table_names' => [ 'custom_field_sections' => 'custom_field_sections', 'custom_fields' => 'custom_fields', 'custom_field_values' => 'custom_field_values', 'custom_field_options' => 'custom_field_options', + 'custom_field_relationships' => 'custom_field_relationships', + 'custom_field_links' => 'custom_field_links', ], 'column_names' => [ 'tenant_foreign_key' => 'tenant_id', @@ -234,37 +286,52 @@ Customize table names and paths: ], ``` +`key_type` shapes the keys of the two tables added in 4.0, `custom_field_relationships` and +`custom_field_links`, so a ULID or UUID application sets it here instead of hand-editing +those published migrations. The tables that predate it keep the hand-edit path. Only the +two relationship slot columns (`from_field_id`, `to_field_id`) follow your `CustomField` +model; `tenant_id`, `relationship_id`, and every morph column follow this setting, so a +ULID or UUID host must set it or inserts fail with a key-type mismatch. + ## Available Features -The package supports these features that can be enabled/disabled: - -| Feature | Description | -|---------|-------------| -| `FIELD_CONDITIONAL_VISIBILITY` | Show/hide fields based on conditions | -| `FIELD_ENCRYPTION` | Encrypt sensitive field values | -| `FIELD_OPTION_COLORS` | Color-coded options for select fields | -| `FIELD_CODE_AUTO_GENERATE` | Auto-generate field codes from names | -| `FIELD_MULTI_VALUE` | Allow multiple values per field | -| `FIELD_UNIQUE_VALUE` | Enforce unique constraint per entity type | -| `FIELD_VALIDATION_RULES` | Enable validation rule configuration | -| `UI_TABLE_COLUMNS` | Show custom fields as table columns | -| `UI_TOGGLEABLE_COLUMNS` | Allow users to toggle column visibility | -| `UI_TOGGLEABLE_COLUMNS_HIDDEN_DEFAULT` | Hide toggleable columns by default | -| `UI_TABLE_FILTERS` | Enable filtering by custom field values | -| `UI_FIELD_WIDTH_CONTROL` | Custom field width per field | -| `UI_SECTION_WIDTH_CONTROL` | Section-level layout width (25/33/50/66/75/100) | -| `SYSTEM_MANAGEMENT_INTERFACE` | Enable the management interface | -| `SYSTEM_MULTI_TENANCY` | Enable multi-tenant isolation | -| `SYSTEM_SECTIONS` | Enable field grouping in sections | +Every feature is listed explicitly in the shipped config, on or off, with the reason for +its default. The defaults below are what 4.0 ships. A published config wins for every flag +it names; a flag it does not name takes the package default from this table, so a config +published before a flag existed inherits that flag instead of running it off. + +| Feature | Default | Description | +|---------|---------|-------------| +| `FIELD_CONDITIONAL_VISIBILITY` | On | Show/hide fields based on conditions | +| `FIELD_ENCRYPTION` | On | Encrypt sensitive field values | +| `FIELD_OPTION_COLORS` | On | Color-coded options for select fields | +| `FIELD_CODE_AUTO_GENERATE` | Off | Auto-generate field codes from names | +| `FIELD_MULTI_VALUE` | Off | Allow multiple values per field | +| `FIELD_UNIQUE_VALUE` | Off | Enforce unique constraint per entity type | +| `FIELD_VALIDATION_RULES` | On | Enable validation rule configuration | +| `FIELD_DESCRIPTION` | On | Help text under a field | +| `FIELD_DESCRIPTION_POSITION` | On | Place that help text above or below the input | +| `MODEL_ATTRIBUTE_CONDITIONS` | Off | Offer the host model's own columns as condition sources | +| `SECTION_CONDITIONAL_VISIBILITY` | On | Show/hide whole sections based on conditions | +| `UI_TABLE_COLUMNS` | On | Show custom fields as table columns | +| `UI_TOGGLEABLE_COLUMNS` | On | Allow users to toggle column visibility | +| `UI_TOGGLEABLE_COLUMNS_HIDDEN_DEFAULT` | Off | Hide toggleable columns by default | +| `UI_TABLE_FILTERS` | On | Enable filtering by custom field values | +| `UI_FIELD_WIDTH_CONTROL` | On | Custom field width per field | +| `UI_SECTION_WIDTH_CONTROL` | On | Section-level layout width (25/33/50/66/75/100) | +| `SYSTEM_MANAGEMENT_INTERFACE` | On | Enable the management interface | +| `SYSTEM_MULTI_TENANCY` | Off | Enable multi-tenant isolation | +| `SYSTEM_SECTIONS` | On | Enable field grouping in sections | +| `SYSTEM_RELATIONSHIPS` | On | Gates the migrations and the upgrade steps only; a record field with a definition reads, writes, and deletes its links either way | ::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. :: ::alert{type="info"} -`UI_SECTION_WIDTH_CONTROL` is disabled by default. Enable it by adding `CustomFieldsFeature::UI_SECTION_WIDTH_CONTROL` to the `->enable(...)` list in your published config. Once enabled, each `SECTION` and `FIELDSET` (not headless sections) can render at a fraction of the row width using the same `CustomFieldWidth` enum used for field-level width. Section widths don't need to sum to 12 — the grid wraps, and sections stack full-width on mobile. +`UI_SECTION_WIDTH_CONTROL` is enabled from 4.0 on, including for a config file published before 4.0: a flag your `features` block does not name takes the package default. Add it to `->disable(...)` to keep it off. Each `SECTION` and `FIELDSET` (not headless sections) can then render at a fraction of the row width using the same `CustomFieldWidth` enum used for field-level width. Section widths don't need to sum to 12: the grid wraps, and sections stack full-width on mobile. -Both section and field widths are fractions of the standard 12-column custom fields grid — `50%` renders as a 6-of-12 column span. If you embed custom fields inside a grid with a different column count, the visual fraction follows that grid instead. +Both section and field widths are fractions of the standard 12-column custom fields grid, so `50%` renders as a 6-of-12 column span. If you embed custom fields inside a grid with a different column count, the visual fraction follows that grid instead. :: ## Configuration Examples diff --git a/docs/content/2.essentials/10.through-relations.md b/docs/content/2.essentials/10.through-relations.md new file mode 100644 index 00000000..1770669e --- /dev/null +++ b/docs/content/2.essentials/10.through-relations.md @@ -0,0 +1,100 @@ +--- +title: Through Relations +description: Show, sort and filter a related record's custom fields in a table whose rows carry none +navigation: + icon: i-lucide-link +--- + +A relation manager listing comments has no custom fields of its own, but the post each +comment belongs to does. `through()` points the table builder at that related record: + +```php +use Relaticle\CustomFields\Facades\CustomFields; + +CustomFields::table() + ->forModel(Post::class) + ->through('post') + ->columns(); +``` + +`forModel()` still names the model the fields come from. `through()` names the relation from +the row record to that model. Filters take the same path. + +## Eager load the relation, or the page will fail + +Read this before you ship a through table. Column names are already dotted +(`custom_fields.code`), so Filament's relationship inference contributes nothing here and it +will not eager load anything for you. Every row resolves its own related record, and under +strict lazy loading (`Model::preventLazyLoading()`) that is a 500, not a slow page. + +Load the relation and the values with the row query: + +```php +use Illuminate\Database\Eloquent\Builder; + +$table->modifyQueryUsing(fn (Builder $query): Builder => $query->with('post.customFieldValues.customField')); +``` + +A relation manager configures it the same way, and takes its filters from the same builder: + +```php +final class CommentsRelationManager extends RelationManager +{ + protected static string $relationship = 'comments'; + + public function table(Table $table): Table + { + return $table + ->modifyQueryUsing(fn (Builder $query): Builder => $query->with('post.customFieldValues.customField')) + ->columns([ + TextColumn::make('body'), + + ...CustomFields::table()->forModel(Post::class)->through('post')->columns(), + ]) + ->filters([ + ...CustomFields::table()->forModel(Post::class)->through('post')->filters(), + ]); + } +} +``` + +## To-one relations only + +A through path accepts `BelongsTo`, `HasOne` and `MorphOne`. Anything else throws +`UnsupportedThroughRelationException`, naming the model, the relation and the reason: + +| Path | Result | +| --- | --- | +| `BelongsTo`, `HasOne`, `MorphOne` to a model with custom fields | Supported | +| `HasMany`, `BelongsToMany` and every other to-many relation | Rejected | +| `MorphTo` | Rejected: the target model varies per row, so no one set of fields describes it | +| A to-one relation to a model without `HasCustomFields` | Rejected | +| A name that is not a relation on the row model | Rejected | + +Filters alone would generalize to any relation through `whereHas`, but sorting would not: a +to-many relation offers no single value to order by. Rather than ship a column set where two +of three behaviours work, the API rejects the path. + +The check runs when the query runs, not when the table is built, because the builder is told +which model the fields come from and never learns which model the rows are. + +## What changes on a through column + +- **State** comes from the related record, and a row whose relation is empty renders blank + rather than erroring. +- **Sorting** correlates the value table to the relation. A `BelongsTo` needs no join: the + foreign key is already on the row table. `HasOne` and `MorphOne` take one more hop. +- **Searching** and **filtering** wrap the constraint in `whereHas`, so they ask the question + of the related record. +- **Visibility conditions** are evaluated against the related record. A condition that reads + another field of the same related record therefore behaves exactly as it does on that + record's own table. + +Two consequences worth knowing: + +A record field (a link to another record) displays and filters through a relation, but it is +not sortable. Ordering one means joining the link ledger through a second relation hop; the +column is marked unsortable rather than emitting an order that is quietly wrong. + +A row with no related record answers no filter, including the negative side of a yes/no +filter. The filter asks about the related record, and there is none to ask. diff --git a/docs/content/2.essentials/11.relationships.md b/docs/content/2.essentials/11.relationships.md new file mode 100644 index 00000000..7138964e --- /dev/null +++ b/docs/content/2.essentials/11.relationships.md @@ -0,0 +1,221 @@ +--- +title: Relationships +description: How record links are defined, stored, and read +navigation: + icon: i-lucide-git-branch +--- + +A record field links one record to another. From 4.0 those links live in a ledger of their +own rather than in a JSON column, which is what makes them sortable, searchable, and safe to +delete a record out of. This page is the model underneath both link field types. + +## Record or Relationship + +Two field types render the ledger, and they are never combined. + +| | Record | Relationship | +|---|---|---| +| Key | `record` | `relationship` | +| Sides that show the link | One | Both | +| Configured with | A target entity and an allow-multiple toggle | A cardinality sentence, a name per side, an optional symmetric toggle | +| Renders as | A searchable select | Chips with avatars, and a picker that can create | +| Slots on its definition | One | One or two | + +Pick **Record** for a link only one side needs to see: a Deal's source Campaign, a Task's +Project. Pick **Relationship** when both entities should show it: People and Companies, a +Post and the Posts it mentions. + +A field keeps the type it was created with. Turning a Record field into a Relationship field +means adding a second slot to its definition, which 4.0 does not offer. + +## Definitions and slots + +Two tables carry all of this. `custom_field_relationships` says what a link means, and +`custom_field_links` records the edges themselves. See +[Data Model](/essentials/data-model) for the columns. + +A definition holds a `code` (the machine-readable semantic, for example `reports_to`), the +entity type at each end, a cardinality, a symmetric flag, and up to two **slots**. A slot is +a `custom_fields` row: the field a user sees on that entity. The number of slots is what +distinguishes the three shapes a definition can take: + +| Slots | Shape | +|---|---| +| 2 | A paired Relationship field, one on each entity. | +| 1 | A one-way Record field. Every field the 4.0 migration created is one of these. | +| 0 | A headless edge type. It never renders a form field; the application creates and reads it directly. | + +Display names live on the `custom_fields` rows. Semantics live on the definition. That is +why a record field's target is no longer a column on the field: `custom_fields.lookup_type` +was dropped in 4.0, and `CustomField::targetEntityType()` answers from the definition +instead. + +```php +use Relaticle\CustomFields\Data\FieldSlotData; +use Relaticle\CustomFields\Data\RelationshipDefinitionData; +use Relaticle\CustomFields\Enums\RelationshipCardinality; +use Relaticle\CustomFields\Services\Relationships\CreateRelationshipDefinition; + +app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'reports_to', + fromEntityType: Person::class, + toEntityType: Person::class, + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Reports To'), + toField: new FieldSlotData(name: 'Direct Reports'), +)); +``` + +The definition and its slots are written in one transaction, and every row is tenant +stamped. `DeleteRelationshipDefinition` and `UpdateRelationshipDefinition` are its siblings. +Leave both slots null for a headless type. + +## Cardinality + +`RelationshipCardinality` has four cases, always read from the `from` end to the `to` end: + +| Case | Value | Reads as | +|---|---|---| +| `OneToOne` | `one_to_one` | One source links to one target. | +| `OneToMany` | `one_to_many` | One source links to many targets. | +| `ManyToOne` | `many_to_one` | Many sources link to one target. | +| `ManyToMany` | `many_to_many` | Many sources link to many targets. | + +Cardinality is what a record field's multiplicity now means. `allow_multiple` and +`max_values` are ignored on a link field: the allow-multiple toggle writes `many_to_many` +when it is on and `many_to_one` when it is off, and there is no count cap because +cardinality has none. + +Enforcement is two layers deep. A validation rule per cardinality produces the friendly +error, and the writer takes a lock on the definition row inside its transaction so two +concurrent writes cannot both fill a single end. A partial unique index over the open edges +is the wall behind both, on PostgreSQL and SQLite; on MySQL the writer alone enforces it, +and the [upgrade guide](/getting-started/upgrade-guide#mysql-and-the-duplicate-edge-guard) +has a generated-column recipe if you want the database-level guard too. + +Changing a cardinality later is allowed. Narrowing a many side to a single one keeps the +first record each side is linked to and asks before closing the rest. Changing the entity +types at either end is not allowed, as in 3.x. + +### Taking a record from its holder + +Where the far end holds a single record, linking a record that another one already holds is +rejected, and the error names the holder. Confirm the replacement explicitly: + +```php +$deal->update([ + 'custom_fields' => [ + 'primary_contact' => ['ids' => [$person->id], 'replace' => true], + ], +]); +``` + +The plain list stays the default and stays unconfirmed. More than one id for a single end is +rejected either way. The displaced edge is closed rather than deleted, so the move stays +auditable. + +## Symmetric relationships + +Some links read the same in both directions: Spouse, Sibling, Colleague. Mark the definition +symmetric and it renders **one** field that both records show, with both slots pointing at +that field. The writer canonicalises each edge before storing it, so a pair is one row no +matter which record was edited, and the duplicate-edge index covers it unchanged. + +There is no sync engine anywhere in this design. One row is one fact, and the other side is +a read of the same row. Inverse drift, loop guards, and cleanup bugs after a one-to-one steal +are structurally impossible rather than handled. + +## Writing links + +The payload contract is the one every field type uses, so the panel, the REST API, imports, +and your own code all keep working: + +```php +$post->update([ + 'custom_fields' => [ + 'related_posts' => [$otherPost->id, $thirdPost->id], + ], +]); +``` + +Four rules govern what happens next: + +1. **Every id is resolved against the target model's own query.** An id that model hides, or + that belongs to another entity type, is a validation error and never becomes an edge. + Your global scopes are therefore the tenant boundary for link targets. +2. **The payload is diffed against the open edges.** Removed ids are closed, added ids are + inserted with their sort order, and unchanged links are never touched. +3. **`[]` and `null` close everything.** Empty is a real value. +4. **An absent key changes nothing.** This is the one place link fields differ from every + other field type, where an absent key clears the value. A `PATCH` that names some fields + leaves the links it did not name alone. + +Everything happens inside the surrounding save transaction. The trait wraps `save()` for +this, so a rejected link rolls the record back with it. If your model overrides `save()`, +call `parent::save()` from it or you lose that. + +## Reading links + +`getCustomFieldValue()` returns the same ordered array of ids it returned in 3.x, so nothing +that reads a record field needs to change: + +```php +$ids = $post->getCustomFieldValue($relatedPostsField); +``` + +Tables and lists batch-load links, so a column costs no N+1. Record columns sort by joining +the target's primary attribute and search with a `whereExists`, both of which the JSON +storage could not do. + +## History + +Unlinking closes an edge, it never deletes it. `active_until` is null while an edge is open +and holds the moment it closed otherwise, so the ledger is a timeline you can query: + +```php +use Relaticle\CustomFields\CustomFields; + +$current = CustomFields::newLinkModel()->newQuery()->active()->get(); + +$closed = CustomFields::newLinkModel() + ->newQuery() + ->whereNotNull('active_until') + ->get(); +``` + +Relinking the same pair inserts a new row rather than reopening the old one, so the record of +what was true when stays intact. + +Deletion follows the same idea. Force-deleting a record sweeps every edge it appears in, so +no id is ever left dangling. Soft-deleting leaves the edges alone: the id still comes back +from `getCustomFieldValue()`, and the surfaces that resolve a record to draw it go through +the model's own query, so a trashed end drops out of chips, columns and entries and returns +when the record does. Deleting a definition takes its links with it, and unpairing one side +of a definition leaves the other side's field and values alone. + +## Provenance + +Every edge records where it came from. `source` is `user`, `import`, `migration`, or +`ai_inferred`, `confidence` is reserved for inferred edges, and `created_by_type` and +`created_by_id` point at whoever made it. + +The actor comes from `Contracts\LinkActorResolverInterface`. The shipped +`AuthenticatedActorResolver` returns the signed-in user, or null when there is none. Rebind +it wherever a write is not made by the signed-in user, an AI agent, an API token, a queued +import, and those writes get stamped correctly. See +[Extending](/essentials/extending#link-actor-resolution). + +Two events carry the full edge and fire after the surrounding transaction commits, so a +listener never sees an edge that was rolled back: + +```php +use Relaticle\CustomFields\Events\RelationshipLinkClosed; +use Relaticle\CustomFields\Events\RelationshipLinkCreated; +``` + +## The relationships feature flag + +`SYSTEM_RELATIONSHIPS` ships on. It gates the two migrations and the upgrade command's +steps, and nothing else: a field that has a definition reads, writes, and deletes its links +regardless of the flag, because no definition can exist without the tables it lives in. An +application with no record fields can leave the flag off and never create the tables. diff --git a/docs/content/2.essentials/3.field-types.md b/docs/content/2.essentials/3.field-types.md index 9572d493..3129075b 100644 --- a/docs/content/2.essentials/3.field-types.md +++ b/docs/content/2.essentials/3.field-types.md @@ -24,6 +24,7 @@ Custom Fields includes 20+ pre-configured field types: | **Currency** | `currency` | Float | Currency formatting with locale support | | **Tags Input** | `tags-input` | Multi-choice | Multiple tags with autocomplete | | **Select** | `select` | Single-choice | Single selection dropdown | +| **Status** | `status` | Single-choice | Single selection dropdown whose options carry a workflow category | | **Multi-Select** | `multi-select` | Multi-choice | Multiple selections | | **Radio** | `radio` | Single-choice | Single choice radio buttons | | **Checkbox** | `checkbox` | Boolean | Simple true/false toggle | @@ -34,7 +35,49 @@ Custom Fields includes 20+ pre-configured field types: | **Date Time** | `date-time` | DateTime | Date and time picker | | **Color Picker** | `color-picker` | Text | Visual color selection | | **File Upload** | `file-upload` | String | File upload with validation | -| **Record** | `record` | Multi-choice | Polymorphic model lookup | +| **Record** | `record` | Multi-choice | One-way link to another entity | +| **Relationship** | `relationship` | Multi-choice | Two-way link, with a field on both ends | + +## Select or Status + +Both are one choice from a list you define, and both render the same select, badge column, +entry and filter. They differ in what an option means. + +Pick **Select** for a plain list: a lead source, a t-shirt size, a region. Its options are +names, nothing more. + +Pick **Status** when the options are the states a record moves through: a task status, a deal +stage, a ticket queue. Every option carries a category from a closed set (`unstarted`, +`started`, `completed`, `cancelled`), so your reports ask for the completed options instead of +matching the label `Done`. See [Option Categories](/essentials/option-categories). + +A Select whose options turned out to be states becomes a Status field by changing `type` on +its row; the options, the values and the ids are untouched, and the categories are then set in +the editor or by your own backfill. + +## Record or Relationship + +Both types link records, store their links in the same ledger, and read them the same way. They +differ in what the person configuring the field is asked, and in what the person filling it in +sees. + +Pick **Record** for a link that only one side needs to see: a Deal's source Campaign, a Task's +Project. You choose the entity it points at and whether it holds more than one record, and the +field renders as a searchable select. The entity is locked once the field exists; turning +multiple off later keeps the first record each row is linked to and asks before unlinking the +rest. + +Pick **Relationship** when both entities should show the link: People and Companies, a Post and +the Posts it mentions. You choose the cardinality in a sentence, name the field that appears on +the other entity, and mark it symmetric when it reads the same in both directions. Its records +render as chips with avatars and a page to open, its picker offers to create a record it could +not find, and where an end holds a single record it confirms a move before taking that record +from whoever holds it. + +A field keeps the type it was created with. Turning a Record field into a Relationship field +means adding a second slot to its definition, which 4.0 does not offer. See +[Relationships](/essentials/relationships) for the definitions, cardinality, symmetry and +history underneath both types. ## Creating Custom Field Types @@ -250,16 +293,22 @@ Control whether users can store multiple values in a single field and enforce un ->supportsMultiValue() ->supportsUniqueConstraint() ->defaultItemValidationRules($rules) -->requiresLookupType() +->requiresRelationship() +->supportsPairing() +->carriesOptionCategories() ``` -**`supportsMultiValue()`** -- Shows an "Allow Multiple Values" toggle in the field editor. When enabled by the user, the field accepts multiple values (e.g., multiple emails or phone numbers) and a "Max Values" input appears. Used by Email, Phone, Link, and Record field types. +**`supportsMultiValue()`** -- Shows an "Allow Multiple Values" toggle in the field editor. When enabled by the user, the field accepts multiple values (e.g., multiple emails or phone numbers) and a "Max Values" input appears. Used by Email, Phone, and Link field types. A record field's multiplicity comes from its relationship cardinality instead. **`supportsUniqueConstraint()`** -- Shows a "Unique per Entity Type" toggle in the field editor. When enabled by the user, a `UniqueCustomFieldValue` validation rule is applied to prevent duplicate values across records of the same entity type. Used by Email, Phone, Link, Text, Textarea, and Number field types. **`defaultItemValidationRules(array $rules)`** -- Validation rules automatically applied to **each individual item** in a multi-value field. These are not user-configurable -- they are hardcoded per field type. Only available for `MULTI_CHOICE` data types; throws `InvalidArgumentException` otherwise. -**`requiresLookupType()`** -- Replaces the user-defined options UI with an entity type selector. The field stores references to records of the selected entity type instead of static option values. Import/export treats these as entity references. Currently used by the Record field type. +**`requiresRelationship()`** -- Replaces the user-defined options UI with a relationship configuration. The field renders one end of a relationship definition and stores links rather than static option values. Import/export treats these as entity references. Used by the Record and Relationship field types. + +**`supportsPairing()`** -- The field configures both ends: cardinality, symmetry, and a field on the other entity. Its surfaces draw chips and the picker confirms a move inline. Without it the type is one-way, and it gets the plain target-and-multiplicity configuration and the plain select. Used by the Relationship field type. + +**`carriesOptionCategories()`** -- Each option of the field means a workflow state, so the options editor asks for a category beside every name and the migrator accepts a `category` key in `options()`. Without it the options are a plain list and a category is rejected. Used by the Status field type. Example -- the Email field type combines these to support multiple unique emails with per-item validation: @@ -312,14 +361,26 @@ return FieldSchema::float() You can define components in two ways: #### 1. Class References (Simple) -For basic components, reference Filament classes directly: +For basic components, reference a class that implements the matching interface +(`FormComponentInterface`, `TableColumnInterface`, `InfolistComponentInterface`), the same +way the shipped field types under `FieldTypeSystem\Definitions` register their components: ```php -->formComponent(TextInput::class) +use Relaticle\CustomFields\Filament\Integration\Components\Forms\TextInputComponent; +use Relaticle\CustomFields\Filament\Integration\Components\Infolists\TextEntry; +use Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns\TextColumn; + +->formComponent(TextInputComponent::class) ->tableColumn(TextColumn::class) ->infolistEntry(TextEntry::class) ``` +A raw Filament class such as `Filament\Forms\Components\TextInput::class` does not work +here: a Filament `Field` needs a name at construction time and does not implement +`FormComponentInterface`, so the factory rejects it. See +[Extending](/essentials/extending) for building your own component on the abstract base +classes. + #### 2. Closures (Flexible) For customized components, use closures that return configured components: @@ -347,6 +408,13 @@ return FieldSchema::singleChoice() }); ``` +Options are added one at a time in the field editor, or pasted in bulk: the **Paste +options** action on the options repeater takes one name per line, up to 100 at a time. +Names are trimmed, blank lines are dropped, and a name that matches one already in the list +is skipped, case-insensitively. Anything past the hundredth line is reported and ignored +rather than silently truncated, which keeps a long paste inside the Livewire payload. Only +names paste; colors and categories are set per row afterwards. + #### Built-in Options Field type provides predefined options: diff --git a/docs/content/2.essentials/4.preset-custom-fields.md b/docs/content/2.essentials/4.preset-custom-fields.md index 2afc132d..a4c6bfec 100644 --- a/docs/content/2.essentials/4.preset-custom-fields.md +++ b/docs/content/2.essentials/4.preset-custom-fields.md @@ -125,7 +125,21 @@ $this->migrator->new( ->create(); ``` -## Adding Lookup Types +Each entry is either the option name on its own, or an array carrying the name plus the +option settings beside it (`color`, and `category` for a single-choice field): + +```php +->options([ + 'Draft', + ['name' => 'Active', 'category' => OptionCategory::Started, 'color' => '#16a34a'], + ['name' => 'Discontinued', 'category' => OptionCategory::Cancelled], +]) +``` + +An array without a `name` throws `InvalidArgumentException`. See +[Option Categories](/essentials/option-categories) for what a category means. + +## Adding Record Fields For fields that reference other models, use the `record` field type with the `lookupType()` method: @@ -148,6 +162,17 @@ $this->migrator->new( ->create(); ``` +`lookupType()` creates a one-way relationship definition with the field as its single slot; +the field itself stores no target. Cardinality comes from `allow_multiple` on the field data +(many-to-many when it is true, many-to-one otherwise), or pass one explicitly: + +```php +->lookupType(User::class, RelationshipCardinality::ManyToMany) +``` + +The ends of a relationship are locked once it exists, so `update()` rejects a `lookup_type` +key. Delete the field and add it again to point it somewhere else. + ## Field Width Options Control field layout using `CustomFieldWidth` enum: diff --git a/docs/content/2.essentials/6.data-model.md b/docs/content/2.essentials/6.data-model.md index 62f90c62..cdc64eda 100644 --- a/docs/content/2.essentials/6.data-model.md +++ b/docs/content/2.essentials/6.data-model.md @@ -18,6 +18,9 @@ The Custom Fields plugin employs a **Hybrid Entity-Attribute-Value (EAV) with Ty | `custom_fields` | one-to-many | `custom_field_options` | Select/checkbox fields have options | | `custom_fields` | one-to-many | `custom_field_values` | Fields store values per entity instance | | Entity (polymorphic) | one-to-many | `custom_field_values` | Entity instances have field values | +| `custom_field_relationships` | one-to-two | `custom_fields` | A definition holds 0, 1, or 2 fields as presentation slots | +| `custom_field_relationships` | one-to-many | `custom_field_links` | A definition owns the edges recorded against it | +| Entity (polymorphic) | one-to-many | `custom_field_links` | Records are linked from either end of an edge | ### Table Schemas @@ -44,10 +47,9 @@ The Custom Fields plugin employs a **Hybrid Entity-Attribute-Value (EAV) with Ty | `id` | bigint | Primary key | | `custom_field_section_id` | bigint | Parent section | | `entity_type` | string | Polymorphic entity class | - | `code` | string | Unique per entity type and section (+ tenant, when multi-tenancy is enabled) — not globally unique, so the same code can exist in two different sections. See [Builder Scoping](/essentials/builder-scoping) | + | `code` | string | Unique per entity type and section (+ tenant, when multi-tenancy is enabled). It is not globally unique, so the same code can exist in two different sections. See [Builder Scoping](/essentials/builder-scoping) | | `name` | string | Display name | | `type` | string | Field type (text, number, etc.) | - | `lookup_type` | string | For lookup fields | | `width` | string | Layout width | | `sort_order` | int | Display order | | `validation_rules` | json | Capability-driven validation config (e.g. `required`, `min_value`, `decimal_places`) | @@ -85,12 +87,57 @@ The Custom Fields plugin employs a **Hybrid Entity-Attribute-Value (EAV) with Ty | `json_value` | json | For complex/array fields | | `tenant_id` | bigint | Optional multi-tenancy | :: + + ::tab{label="Relationships"} + Created by the `SYSTEM_RELATIONSHIPS` migrations. + + | Column | Type | Description | + |--------|------|-------------| + | `id` | bigint | Primary key (`database.key_type`) | + | `code` | string | Machine-readable semantic, e.g. `reports_to`; unique per tenant | + | `from_entity_type` | string | Polymorphic class at the from end | + | `to_entity_type` | string | Polymorphic class at the to end; may equal the from end | + | `cardinality` | string | `one_to_one`, `one_to_many`, `many_to_one`, `many_to_many`, read from the `from` end to the `to` end | + | `from_field_id` | bigint | Optional presentation slot; keyed off the `CustomField` model | + | `to_field_id` | bigint | Optional presentation slot on the other end | + | `is_symmetric` | bool | One field, canonical edge ordering (Spouse, Sibling) | + | `tenant_id` | bigint | Optional multi-tenancy | + + Two slots render a paired relationship, one renders a one-way record field, and zero is a + headless edge type that never appears in a form. Each slot is a `custom_fields` row, and its + `type` is the face it renders: `record` for the one-way field, `relationship` for the paired + one. + :: + + ::tab{label="Links"} + Created by the `SYSTEM_RELATIONSHIPS` migrations. + + | Column | Type | Description | + |--------|------|-------------| + | `id` | bigint | Primary key (`database.key_type`) | + | `relationship_id` | bigint | Owning definition; links cascade with it | + | `from_entity_type`, `from_entity_id` | string, bigint | The from end of the edge | + | `to_entity_type`, `to_entity_id` | string, bigint | The to end of the edge | + | `sort_order` | int | Preserves list order | + | `active_from` | datetime | When the edge opened, written by PHP | + | `active_until` | datetime | Null while the edge is current; unlinking closes it rather than deleting | + | `created_by_type`, `created_by_id` | string, bigint | The actor: a user, an agent, an API token, an import | + | `source` | string | `user`, `import`, `migration`, or `ai_inferred` | + | `confidence` | double | Reserved for inferred edges | + | `tenant_id` | bigint | Optional multi-tenancy | + + The ledger is temporal: a closed edge stays queryable as history, and re-linking the same + pair inserts a new row. A partial unique index over the open edges blocks duplicates on + PostgreSQL and SQLite; see the [upgrade guide](/getting-started/upgrade-guide) for the + MySQL equivalent, and [Relationships](/essentials/relationships) for how the two tables + are written and read. + :: :: ## Design Philosophy ### Type-Safe Flexibility -The schema uses multiple typed columns in `custom_field_values` rather than a single text column. This eliminates costly type conversions, enables native database sorting/filtering, and maintains data integrity through database-level constraints. When you store an integer, it's actually stored as an integer—not a string that needs parsing. +The schema uses multiple typed columns in `custom_field_values` rather than a single text column. This eliminates costly type conversions, enables native database sorting/filtering, and maintains data integrity through database-level constraints. When you store an integer, it's actually stored as an integer, not a string that needs parsing. ### Hierarchical Organization Fields are organized into sections, providing logical grouping essential for complex forms. This two-level hierarchy supports progressive disclosure in UIs and administrative organization without adding complexity to simple use cases. @@ -100,7 +147,7 @@ Strategic composite indexes optimize the most common query patterns: entity look ## Why This Schema Design -**Polymorphic Flexibility**: Any model can have custom fields without tight coupling or migration dependencies. Add custom fields to `Product`, `User`, `Order`—anything implementing the `HasCustomFields` interface. +**Polymorphic Flexibility**: Any model can have custom fields without tight coupling or migration dependencies. Add custom fields to `Product`, `User`, `Order`, or anything implementing the `HasCustomFields` interface. **Multi-Tenant Isolation**: Optional tenant awareness is built into the core schema, not bolted on later. When enabled, all data is automatically isolated between tenants while maintaining query performance. diff --git a/docs/content/2.essentials/7.builder-scoping.md b/docs/content/2.essentials/7.builder-scoping.md index 51dc6389..e668294b 100644 --- a/docs/content/2.essentials/7.builder-scoping.md +++ b/docs/content/2.essentials/7.builder-scoping.md @@ -20,10 +20,10 @@ CustomFields::form() ->build(); ``` -Passing `[]` (the default) means "no scope" — existing call sites that never call +Passing `[]` (the default) means "no scope", so existing call sites that never call `onlySections()` are unaffected. -This exists for consumers that version their form definitions — for example, cloning a +This exists for consumers that version their form definitions, for example cloning a section (and its fields) per form version. Field codes are normally unique per entity type, so two versions of "the same field" would collide on `code` unless resolution is scoped by section. `onlySections()` lets each version's builder only see its own @@ -31,18 +31,18 @@ section(s), so the same code can live in more than one section without one bleed the other's schema. `onlySections()` is inherited by every builder from `BaseBuilder`, including through -`FormBuilder::build()` / `InfolistBuilder::build()` — the scope is threaded through +`FormBuilder::build()` / `InfolistBuilder::build()`. The scope is threaded through `FormContainer` / `InfolistContainer` as well, so it applies whether you call `->build()` or `->values()`. -### The persistence contract — read this before relying on section-scoped codes +### The persistence contract: read this before relying on section-scoped codes `onlySections()` narrows *resolution* (which fields get loaded onto a form, infolist, or table). It does not change how values are *saved*. `UsesCustomFields::saveCustomFields()` iterates the model's custom-field-values relationship and writes each submitted value by **field code**. If two sections share a code and that relationship isn't scoped to match `onlySections()`, `saveCustomFields()` will silently write the same value to **both** -field rows — a data-corrupting outcome that has nothing to do with whether resolution +field rows. That is a data-corrupting outcome that has nothing to do with whether resolution scoping itself is working correctly. If you use `onlySections()`, scope your model's custom-field-values relationship @@ -68,16 +68,16 @@ CodeGenerator::resolveUniquenessScopeUsing( The callback receives: -- `$entityType` — the entity the field or section belongs to. -- `$type` — `'field'` or `'section'`, so you can scope differently per kind of code. -- `$sectionId` — the section the code is being generated within, or `null` when there +- `$entityType`: the entity the field or section belongs to. +- `$type`: `'field'` or `'section'`, so you can scope differently per kind of code. +- `$sectionId`: the section the code is being generated within, or `null` when there isn't one (for example, the sectionless field-management table). -Return `null` to leave the uniqueness check global — the default, backward-compatible +Return `null` to leave the uniqueness check global. That is the default, backward-compatible behavior. Return a closure to narrow it: the closure receives the in-progress `Builder` and must **return** the query to apply. `where()`-style mutation also works (it returns -the same instance), but a closure that hands back a different instance — e.g. -`$query->clone()->where(...)` — is honored too, since the return value is always what's +the same instance), but a closure that hands back a different instance (for example +`$query->clone()->where(...)`) is honored too, since the return value is always what's used. Register the callback once, typically in a service provider's `boot()` method. diff --git a/docs/content/2.essentials/8.extending.md b/docs/content/2.essentials/8.extending.md new file mode 100644 index 00000000..62939b0c --- /dev/null +++ b/docs/content/2.essentials/8.extending.md @@ -0,0 +1,392 @@ +--- +title: Extending +description: Every supported extension point, and why everything else is final +navigation: + icon: i-lucide-puzzle +--- + +Custom Fields extends through the seams listed on this page. Every other class under +`src/` is `final`, and an architecture test in the package keeps that true. + +::alert{type="warning"} +Un-finalizing an internal class is not how a new use case gets supported. If your case is +not covered below, open an issue asking for a seam. A seam is a contract we test and +document; an open class is a private detail that becomes a breaking change the next time +it is refactored. +:: + +## Custom field types + +A field type owns one entry in the field-type dropdown and decides which form, table, +infolist, and filter components render it. + +### Generate one + +```bash +php artisan make:field-type StarRating +``` + +The command writes `app/Filament/FieldTypes/StarRatingFieldType.php`, extending +`BaseFieldType`. See [Field Types](/essentials/field-types) for the full `FieldSchema` API. + +```php +use Filament\Forms\Components\Select; +use Relaticle\CustomFields\FieldTypeSystem\BaseFieldType; +use Relaticle\CustomFields\FieldTypeSystem\FieldSchema; +use Relaticle\CustomFields\Models\CustomField; + +final class StarRatingFieldType extends BaseFieldType +{ + public function configure(): FieldSchema + { + return FieldSchema::numeric() + ->key('acme-star-rating') + ->label('Star Rating') + ->icon('heroicon-o-star') + ->formComponent(fn (CustomField $customField): Select => Select::make($customField->getFieldName()) + ->label($customField->name) + ->options([1 => '1', 2 => '2', 3 => '3', 4 => '4', 5 => '5'])) + ->priority(45); + } +} +``` + +A field type without a `formComponent()` throws as soon as a form renders it, so the +generated stub always sets one. `tableColumn()` and `infolistEntry()` are optional: a field +type that sets neither simply never appears in a table or on a record page. + +### Register it + +```php +use Relaticle\CustomFields\CustomFieldsPlugin; + +CustomFieldsPlugin::make() + ->registerFieldTypes([ + StarRatingFieldType::class, + ]), +``` + +### Subclass a shipped one + +Field types are keyed by `key()`, and the last registration for a key wins, so a subclass +registered under the same key replaces the packaged definition while inheriting everything +it does not override. + +```php +use Relaticle\CustomFields\FieldTypeSystem\Definitions\DateTimeFieldType as BaseDateTimeFieldType; +use Relaticle\CustomFields\FieldTypeSystem\FieldSchema; + +final class DateTimeFieldType extends BaseDateTimeFieldType +{ + public function configure(): FieldSchema + { + return parent::configure()->tableColumn(TenantAwareDateTimeColumn::class); + } +} +``` + +Every class under `FieldTypeSystem\Definitions` stays open for exactly this. + +## Custom Filament components + +Components are resolved from the container, so constructor injection works. One instance is +reused for every field of that type, so keep them stateless and take everything from the +`CustomField` argument. + +The shipped concrete components are final: build yours on the abstract base rather than +subclassing one of them. `Tables\Columns\DateTimeColumn`, `Tables\Columns\IconColumn`, and +the three custom input components (`MultiValueInputComponent`, `PhoneInputComponent`, +`RecordSelectInputComponent`) are the exceptions and stay open. The package extends the last +one itself: the relationship type's picker is the record select plus chips, an inline move +confirmation, and provenance. + +| Surface | Base class | Method to implement | +|---|---|---| +| Form field | `Filament\Integration\Base\AbstractFormComponent` | `create(CustomField $customField): Field` | +| Infolist entry | `Filament\Integration\Base\AbstractInfolistEntry` | `make(CustomField $customField, ?Model $record = null): Entry` | +| Table column | `Filament\Integration\Base\AbstractTableColumn` | `make(CustomField $customField, ?Model $record = null): Column` | +| Table filter | `Filament\Integration\Base\AbstractTableFilter` | `make(CustomField $customField, ?Model $record = null, ?string $through = null): BaseFilter` | + +`AbstractFormComponent` implements `make()` itself (visibility, validation, description, +width, encryption) and calls your `create()`, so a form component only describes the field. + +```php +use Filament\Tables\Columns\Column; +use Filament\Tables\Columns\TextColumn; +use Illuminate\Database\Eloquent\Model; +use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; +use Relaticle\CustomFields\Models\CustomField; + +final class TenantAwareDateTimeColumn extends AbstractTableColumn +{ + public function make(CustomField $customField, ?Model $record = null): Column + { + return TextColumn::make($customField->getFieldName()) + ->label($customField->name) + ->dateTime(config('app.datetime_format')); + } +} +``` + +Register it on a field type, either on your own or on a subclass of a shipped definition: + +```php +FieldSchema::dateTime() + ->key('date-time') + ->tableColumn(TenantAwareDateTimeColumn::class) + ->infolistEntry(TenantAwareDateTimeEntry::class); +``` + +Implementing `Contracts\FormComponentInterface`, `InfolistComponentInterface`, +`TableColumnInterface`, or `TableFilterInterface` directly is supported too; the base +classes only save you the boilerplate. + +## Validation capabilities + +A capability is one configurable rule in the field editor (minimum value, maximum length, +accepted file types). Implement `Contracts\ValidationCapabilityInterface` and list it on +the field type. + +```php +use Filament\Forms\Components\Field; +use Filament\Forms\Components\TextInput; +use Filament\Schemas\Components\Component; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; + +final readonly class DivisibleByCapability implements ValidationCapabilityInterface +{ + public function key(): string + { + return 'divisible_by'; + } + + public function label(): string + { + return __('acme.validation.divisible_by'); + } + + /** @return array */ + public function formSchema(string $statePath): array + { + return [TextInput::make($statePath.'.divisible_by')->numeric()]; + } + + public function applyToComponent(Field $component, mixed $value): void + { + // + } + + /** @return array */ + public function toRules(mixed $value): array + { + return $value === null ? [] : ['multiple_of:'.$value]; + } +} +``` + +```php +FieldSchema::numeric() + ->key('acme-quantity') + ->withValidationCapabilities(DivisibleByCapability::class); +``` + +The capability form only appears when the `FIELD_VALIDATION_RULES` feature is enabled. + +## Value resolution + +`Contracts\ValueResolverInterface` turns a stored value into the value exports and +read surfaces see. It is a container singleton, so rebind it to replace the packaged +resolver: + +```php +use Relaticle\CustomFields\Contracts\ValueResolverInterface; + +$this->app->singleton(ValueResolverInterface::class, AcmeValueResolver::class); +``` + +## Link actor resolution + +`Contracts\LinkActorResolverInterface` decides which model is stamped on a relationship +link as its creator. The packaged `AuthenticatedActorResolver` returns the authenticated +user, or null when there is none. Rebind it wherever a write is not made by the signed-in +user: an AI agent, an API token, an import run. + +```php +use Relaticle\CustomFields\Contracts\LinkActorResolverInterface; + +$this->app->singleton(LinkActorResolverInterface::class, AcmeAgentActorResolver::class); +``` + +The resolver runs once per link write, inside the writer's transaction, so it must be +cheap and must never throw for an unauthenticated request. + +## Link targets and tenancy + +Every id in a record field's payload is checked against the target model's own query before +an edge is stored, so a target that model hides is refused as unknown. That makes the host's +global scopes the tenant boundary for link targets: a multi-tenant application must give its +linkable models a tenant scope, because the package never guesses how a host record is +owned. + +`RelationshipLinkCreated` and `RelationshipLinkClosed` are dispatched after the surrounding +transaction commits, so a listener never sees an edge that was rolled back. + +## The model swap registry + +These models stay open so an application can add its own traits, casts, relations, or +tenancy behaviour. Register the replacements in a service provider's `register()` method, +before anything resolves a model. + +```php +use Relaticle\CustomFields\CustomFields; + +CustomFields::useCustomFieldModel(\App\Models\CustomField::class); +CustomFields::useSectionModel(\App\Models\CustomFieldSection::class); +CustomFields::useOptionModel(\App\Models\CustomFieldOption::class); +CustomFields::useValueModel(\App\Models\CustomFieldValue::class); +CustomFields::useRelationshipModel(\App\Models\CustomFieldRelationship::class); +CustomFields::useLinkModel(\App\Models\CustomFieldLink::class); +``` + +Your subclass must extend the packaged model. The package never instantiates a model +class directly: it goes through `CustomFields::newCustomFieldModel()` and its siblings, +which is what makes the swap complete. + +`QueryBuilders\CustomFieldQueryBuilder` stays open for the same reason: it is what +`CustomField::newEloquentBuilder()` returns, so a subclassed model can return its own +extension of it and keep the packaged query methods. + +`Models\Scopes\ActivableScope` is also non-final, but only because +`CustomFieldsActivableScope` extends it inside the package. Both scopes are instantiated +directly where they are applied, so subclassing one gives you nothing to register: it is +not a seam. + +## Model traits + +`Models\Concerns\UsesCustomFields` (plus the `Models\Contracts\HasCustomFields` interface) +makes a model custom-field capable. `Concerns\InteractsWithCustomFields` adds the +custom-field state handling to a Filament page. Both are traits, so they compose rather +than inherit. + +## Preset field migrations + +An application that ships its own fields declares them in a migration extending +`Filament\Integration\Migrations\CustomFieldsMigration`, which hands you a +`CustomFieldsMigrator` as `$this->migrator`. That base class is open for exactly this, and +`php artisan make:custom-fields-migration AddOrderFields` generates one. + +```php +use Relaticle\CustomFields\Data\CustomFieldData; +use Relaticle\CustomFields\Filament\Integration\Migrations\CustomFieldsMigration; + +return new class extends CustomFieldsMigration +{ + public function up(): void + { + $this->migrator->new( + model: Order::class, + fieldData: new CustomFieldData( + name: 'Additional Information', + code: 'additional_information', + type: 'text', + systemDefined: true, + ), + )->create(); + } +}; +``` + +[Preset Custom Fields](/essentials/preset-custom-fields) documents the full migrator API, +including sections, options, and updating a field that already exists. + +## The management page + +Filament reads the slug, cluster, sub-navigation, and heading off the page class, so +changing any of them means registering your own page. Subclass the packaged one and pass +it to the plugin; all packaged behaviour is inherited. + +```php +use Filament\Panel; +use Relaticle\CustomFields\Filament\Management\Pages\CustomFieldsManagementPage; + +final class CustomFields extends CustomFieldsManagementPage +{ + public static function getSlug(?Panel $panel = null): string + { + return 'settings/custom-fields'; + } +} +``` + +The packaged page resolves its own slug from `custom-fields.management.slug`, so override +`getSlug()` rather than the `$slug` property, which it never reads. + +```php +CustomFieldsPlugin::make()->managementPage(CustomFields::class), +``` + +Passing a class that does not extend `CustomFieldsManagementPage` throws. + +## Feature flags + +`FeatureConfigurator` in `config/custom-fields.php` decides which features exist for your +application. Every flag is listed explicitly in the shipped config with the reason for its +default; see [Configuration](/essentials/configuration) for the table. + +```php +use Relaticle\CustomFields\Enums\CustomFieldsFeature; +use Relaticle\CustomFields\FeatureSystem\FeatureConfigurator; + +'features' => FeatureConfigurator::configure() + ->enable( + // every flag you want on, the shipped list plus your changes + CustomFieldsFeature::FIELD_MULTI_VALUE, + ) + ->disable( + // every flag you want off + CustomFieldsFeature::UI_TABLE_FILTERS, + ), +``` + +Change a flag by moving it between these two lists in the published config. A flag missing +from both lists takes its package default, so naming only the flags you want to change is +enough; the [Configuration](/essentials/configuration) table lists every default. + +## Entity configuration + +`EntityConfigurator` decides which models can carry custom fields, by discovery or by an +explicit list. + +```php +use Relaticle\CustomFields\EntitySystem\EntityConfigurator; + +'entity_configuration' => EntityConfigurator::configure() + ->discover(app_path('Models')) + ->exclude([\App\Models\User::class]) + ->cache(enabled: true, ttl: 3600), +``` + +## Tenant resolution + +Multi-tenant applications that do not use Filament's tenant, or that resolve a tenant +their own way, register a resolver instead of subclassing anything. + +```php +use Relaticle\CustomFields\CustomFields; + +CustomFields::resolveTenantUsing(fn (): ?int => auth()->user()?->company_id); +``` + +## Everything else is final + +Services, factories, resolvers, builders, data objects, exceptions, observers, scopes, +middleware, facades, the management form schemas, and the Livewire components are all +closed, and the package's architecture test fails if one of them is opened without being +added to this page. + +Every service provider is final: they are registration wiring, not seams. +`CustomFieldsPlugin` is configuration: call its methods, do not subclass it. Nothing in the +package reads a subclass of it. + +If you need behaviour that none of the seams above reaches, that is a gap in this page. +Open an issue and we will add the seam. diff --git a/docs/content/2.essentials/9.option-categories.md b/docs/content/2.essentials/9.option-categories.md new file mode 100644 index 00000000..f04e1e48 --- /dev/null +++ b/docs/content/2.essentials/9.option-categories.md @@ -0,0 +1,165 @@ +--- +title: Option Categories +description: Give the options of a Status field a machine-readable meaning so consumers stop matching labels +navigation: + icon: i-lucide-flag +--- + +A select option is free text. Nothing tells your code that `Closed Won` ends a deal and +`Discovery` does not, so every report, digest, and integration ends up matching labels, and +the first rename or translation breaks it. + +The **Status** field type answers that. It is a single-choice field whose options are the +states a record moves through, and every option carries a category: a machine-readable +meaning stored beside it on `settings.category`. Labels stay free: rename `Done` to +`Shipped`, translate it, and the category still says what it means. + +## Status or Select + +Pick **Status** when the options are states: a task status, a deal stage, a ticket queue. +Pick **Select** when they are a plain list: a lead source, a region, a t-shirt size. + +Everything a value touches is the same in both. A Status field renders the same select +input, the same badge column, the same entry and the same filter, and it stores a single +option id exactly as a Select does. The only difference is in the field editor, where a +Status field asks for a category beside every option name and a Select never does. + +## The vocabulary + +`Relaticle\CustomFields\Enums\OptionCategory` is a closed set of four: + +| Case | Value | Meaning | +|---|---|---| +| `OptionCategory::Unstarted` | `unstarted` | Queued, nobody has picked it up | +| `OptionCategory::Started` | `started` | Work is under way | +| `OptionCategory::Completed` | `completed` | Finished successfully | +| `OptionCategory::Cancelled` | `cancelled` | Ended without succeeding | + +Three rules follow from that set: + +- **A category is optional.** `null` means unknown, never "not done". An option nobody has + categorised yet stays `null`, and no consumer should read that as a state. +- **Many options may share one category.** `Closed Won` and `Won Back` are both + `completed`, so every read returns a collection, never a single option. +- **Only a Status field carries one.** Nothing writes a category on any other field type: + the editor does not offer the column, and the migrator rejects the key. + +### Terminal categories + +`completed` and `cancelled` are terminal: reaching either one ends the record's journey. +`isTerminal()` names that, so you never have to spell the pair out: + +```php +use Relaticle\CustomFields\Enums\OptionCategory; + +OptionCategory::Started->isTerminal(); // false +OptionCategory::Completed->isTerminal(); // true +OptionCategory::Cancelled->isTerminal(); // true +``` + +The two-outcome ending fits without a second axis: won is `completed`, lost is +`cancelled`. Direction follows for free. Within one category a higher `sort_order` is +further forward, and entering a terminal category is a close. + +## Setting a category + +The field editor shows a **Category** column beside each option of a Status field, always. +Leaving it empty is allowed, and the placeholder reads `Uncategorised` to say what that +means: the option has no state yet, not that it is unfinished. + +There is no feature flag. A field type is offered or withheld through the type registry you +already control, so an application with no use for workflow states leaves the type out: + +```php +'field_type_configuration' => FieldTypeConfigurator::configure() + ->disabled(['status']), +``` + +## Reading categories + +`CustomField::optionsInCategory()` returns one field's options in that category, in +`sort_order`: + +```php +use Relaticle\CustomFields\Enums\OptionCategory; + +$wonStages = $stageField->optionsInCategory(OptionCategory::Completed); + +$wonStageIds = $wonStages->modelKeys(); +``` + +`whereCategory()` is the query-builder form, for joins and aggregates: + +```php +use Relaticle\CustomFields\CustomFields; + +$completedOptionIds = CustomFields::newOptionModel() + ->query() + ->whereCategory(OptionCategory::Completed) + ->pluck('id'); +``` + +The category is also on the settings object of any loaded option: + +```php +$option->settings->category?->isTerminal(); +``` + +Both reads work on any field. On a field that is not a Status field they simply come back +empty, because neither the editor nor the migrator writes a category there. + +## Seeding a Status field + +Declare the type as `status` and pass an array instead of a string to the migrator's +`options()` to seed a category (and a color) with the field: + +```php +use Relaticle\CustomFields\Data\CustomFieldData; +use Relaticle\CustomFields\Enums\OptionCategory; +use Relaticle\CustomFields\FieldTypeSystem\Definitions\StatusFieldType; +use Relaticle\CustomFields\Filament\Integration\Migrations\CustomFieldsMigrator; + +app(CustomFieldsMigrator::class)->new( + model: Opportunity::class, + fieldData: new CustomFieldData( + name: 'Stage', + code: 'stage', + type: StatusFieldType::KEY, + ), +)->options([ + 'Discovery', + ['name' => 'Proposal', 'category' => OptionCategory::Started], + ['name' => 'Closed Won', 'category' => OptionCategory::Completed, 'color' => '#16a34a'], + ['name' => 'Closed Lost', 'category' => OptionCategory::Cancelled], +])->create(); +``` + +Both forms mix freely in one call. A plain string is the option name, and an array carries +the name plus the option settings beside it (`category`, `color`). An array without a +`name` throws `InvalidArgumentException`, and so does a `category` on a field whose options +are not states. `category` accepts an `OptionCategory` case or its string value; anything +else is rejected rather than stored. + +The package seeds no options of its own, so it ships no backfill. Options your tenants +already created stay `null` until you set them, either in the editor or with your own +command. + +## Turning a Select into a Status field + +Storage is identical, so a Select whose options turned out to be states becomes a Status +field by changing `type` on the row. The options, their ids and every stored value are +untouched: + +```php +app(CustomFieldsMigrator::class) + ->find(Opportunity::class, 'stage') + ->update(['type' => StatusFieldType::KEY]); +``` + +Set the categories afterwards, on the option rows themselves or in the editor. Do not +re-declare them by passing `options()` to that update: the migrator replaces an updated +field's options wholesale, so the rows get new ids and every value pointing at the old ones +is orphaned. + +The package ships no conversion command, because only your application knows which of its +fields are workflows and what each of their options means. diff --git a/docs/content/3.community/2.contributing.md b/docs/content/3.community/2.contributing.md index d164833d..ab02d563 100644 --- a/docs/content/3.community/2.contributing.md +++ b/docs/content/3.community/2.contributing.md @@ -26,9 +26,6 @@ cd custom-fields composer install npm install -# Set up testing environment -cp phpunit.xml.dist phpunit.xml - # Run tests to verify setup composer test ``` @@ -71,6 +68,7 @@ composer test:types - **Follow PSR-12** coding standards - **Write tests** for all new features (aim for 80%+ coverage) - **Use type declarations** where possible +- **Keep `composer test:types` clean** at PHPStan level 6, with no baseline. Two kinds of ignore are accepted in `phpstan.neon`: the `trait.unused` identifier (library traits exist for hosts to consume) and a finding that differs between the CI dependency legs, which must name the drift in a comment - **Document complex logic** with clear comments - **Keep methods small** and focused on a single responsibility @@ -94,6 +92,49 @@ composer test-coverage composer test:arch ``` +### Database Matrix + +CI runs the suite against SQLite, PostgreSQL, and MySQL, since parts of the package (unique +indexes, JSON columns, locking) behave differently per driver. `tests/TestCase.php` reads +`DB_CONNECTION` (defaults to an in-memory SQLite database) and, for `pgsql` and `mysql`, +`DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`, and `DB_PASSWORD` from the environment. + +To run against PostgreSQL or MySQL locally, create an empty database and point the suite at it: + +```bash +# PostgreSQL +createdb -U root -h 127.0.0.1 custom_fields_test +DB_CONNECTION=pgsql DB_HOST=127.0.0.1 DB_PORT=5432 DB_DATABASE=custom_fields_test DB_USERNAME=root DB_PASSWORD= vendor/bin/pest + +# MySQL +mysql -u root -h 127.0.0.1 -e "CREATE DATABASE custom_fields_test" +DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=custom_fields_test DB_USERNAME=root DB_PASSWORD= vendor/bin/pest +``` + +`--parallel` only works with SQLite here: each worker gets its own in-memory database for free, +while PostgreSQL and MySQL workers race against one shared database (Pest never enables +Laravel's per-worker databases for a testbench package). `composer test` runs `pest --parallel`, +so keep it on SQLite; on the other two drivers run `vendor/bin/pest` without `--parallel`, which +is what every CI leg does. + +### UI Flavor + +Five UI surfaces (the relationship configurator, record chips, the record picker, the type +picker, and the attribute table) render in two presentations: `polished` (the package's own +Blade views, the default) and `native` (stock Filament). Logic never forks, only views, so both +presentations run the same Livewire classes and the same tests. + +`tests/TestCase.php` reads `CUSTOM_FIELDS_UI_FLAVOR` (defaults to `polished`) and sets +`custom-fields.ui.flavor` from it, so the whole suite runs once per flavor: + +```bash +CUSTOM_FIELDS_UI_FLAVOR=native vendor/bin/pest +``` + +CI runs one extra leg for `native` beside the database matrix. A test that reads markup only +one presentation draws asks `rendersPolished()` (in `tests/Helpers.php`) and asserts the other +flavor's equivalent, rather than skipping. + ### Documentation - Update documentation for new features diff --git a/docs/content/3.community/3.license.md b/docs/content/3.community/3.license.md index 3b771851..b2b1fd5a 100644 --- a/docs/content/3.community/3.license.md +++ b/docs/content/3.community/3.license.md @@ -9,12 +9,12 @@ Custom Fields is dual-licensed. Choose the license that fits your project. ## Commercial License -A commercial license lets you use Custom Fields in private, closed-source applications. All tiers include every feature — the only difference is how many domains you can deploy to. +A commercial license lets you use Custom Fields in private, closed-source applications. All tiers include every feature. The only difference is how many domains you can deploy to. What you get: -- **Private use** — no obligation to open-source your application -- **1 year of updates** — access to all new features and bug fixes -- **Priority support** — via email and Discord +- **Private use**: no obligation to open-source your application +- **1 year of updates**: access to all new features and bug fixes +- **Priority support**: via email and Discord [View pricing plans](/#pricing) to choose the right tier for your project. @@ -26,7 +26,7 @@ What you get: ## Open Source (AGPL-3.0) -Custom Fields is also available under [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.en.html) — free to use if your **entire application** is open source. This means all source code that interacts with Custom Fields must be publicly available under a compatible license. +Custom Fields is also available under [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.en.html), free to use if your **entire application** is open source. This means all source code that interacts with Custom Fields must be publicly available under a compatible license. --- diff --git a/docs/content/index.md b/docs/content/index.md index acda6295..92e79b89 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -75,6 +75,17 @@ Why choose Custom Fields? Text, numbers, dates, selects, rich editors, tags, color pickers, and more. ::: + :::u-page-feature + --- + icon: i-lucide-git-branch + --- + #title + Record Relationships + + #description + Link records to records, one-way or paired, on a ledger that sorts, searches, and keeps history. + ::: + :::u-page-feature --- icon: i-lucide-shield-check diff --git a/phpstan.neon b/phpstan.neon index 4d51c2ef..ffc8ba15 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,7 +2,7 @@ includes: - vendor/larastan/larastan/extension.neon parameters: - level: 5 + level: 6 treatPhpDocTypesAsCertain: false # nullsafe.neverNull inference differs between larastan versions (e.g. 3.9 vs 3.10), so some # defensive `@phpstan-ignore nullsafe.neverNull` annotations are "unmatched" on newer larastan @@ -12,8 +12,6 @@ parameters: - src - tests excludePaths: - - src/Filament/Management/Pages/CustomFieldsManagementPage.php - - src/Livewire - tests ignoreErrors: # Ignore unused trait warnings for library traits meant to be consumed by package users @@ -27,5 +25,11 @@ parameters: identifier: argument.type path: src/Filament/Integration/Components/Tables/Columns/* reportUnmatched: false + # Laravel 13 made the Scope interface generic; Laravel 12 has the template on apply() only, + # so @implements Scope is required on one CI leg and rejected on the other + - + identifier: generics.notGeneric + path: src/Models/Scopes/* + reportUnmatched: false parallel: maximumNumberOfProcesses: 3 \ No newline at end of file diff --git a/resources/boost/skills/custom-fields-development/SKILL.md b/resources/boost/skills/custom-fields-development/SKILL.md index ed612a06..cd2ee09a 100644 --- a/resources/boost/skills/custom-fields-development/SKILL.md +++ b/resources/boost/skills/custom-fields-development/SKILL.md @@ -150,6 +150,7 @@ public function getColumns(): array | Date | `date` | date_value | | DateTime | `date-time` | datetime_value | | Select | `select` | string_value | +| Status | `status` | string_value | | Multi-Select | `multi-select` | json_value | | Checkbox | `checkbox` | boolean_value | | Checkbox List | `checkbox-list` | json_value | @@ -160,6 +161,7 @@ public function getColumns(): array | Color Picker | `color-picker` | text_value | | File Upload | `file-upload` | string_value | | Record Select | `record` | json_value | +| Relationship | `relationship` | json_value | ### Field Type Key Naming @@ -217,6 +219,9 @@ use Relaticle\CustomFields\FeatureSystem\FeatureConfigurator; | `SYSTEM_SECTIONS` | Organize fields into sections | | `SYSTEM_MULTI_TENANCY` | Tenant isolation for fields | +Every flag and its shipped default lives in `config/custom-fields.php`, each with the +reason for that default; read it there rather than assuming one. + ## Configuration ### Entity Discovery diff --git a/resources/dist/custom-fields.css b/resources/dist/custom-fields.css index 691c25aa..fe02d186 100644 --- a/resources/dist/custom-fields.css +++ b/resources/dist/custom-fields.css @@ -1 +1 @@ -/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){.custom-fields-component *,.custom-fields-component ::backdrop,.custom-fields-component :after,.custom-fields-component :before{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-tracking:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-ease:initial;--tw-divide-x-reverse:0;--tw-content:"";--tw-outline-style:solid;--tw-space-x-reverse:0}}}@layer theme{.custom-fields-component :host,.custom-fields-component :root{--font-mono:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-green-500:oklch(72.3% .219 149.579);--color-sky-400:oklch(74.6% .16 232.661);--color-gray-100:var(--gray-100);--color-gray-200:var(--gray-200);--color-gray-300:var(--gray-300);--color-gray-400:var(--gray-400);--color-gray-500:var(--gray-500);--color-gray-600:var(--gray-600);--color-gray-700:var(--gray-700);--color-gray-900:var(--gray-900);--color-gray-950:var(--gray-950);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:1.33333;--text-sm:.875rem;--text-sm--line-height:1.42857;--text-base:1rem;--text-base--line-height:1.5;--text-lg:1.125rem;--text-lg--line-height:1.55556;--text-xl:1.25rem;--text-xl--line-height:1.4;--text-2xl:1.5rem;--text-2xl--line-height:1.33333;--text-3xl:1.875rem;--text-3xl--line-height:1.2;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--leading-relaxed:1.625;--leading-loose:2;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in:cubic-bezier(.4,0,1,1);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--default-mono-font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-primary-400:var(--primary-400)}}@layer base{.custom-fields-component *,.custom-fields-component ::backdrop,.custom-fields-component :after,.custom-fields-component :before{border:0 solid;box-sizing:border-box;margin:0;padding:0}.custom-fields-component ::file-selector-button{border:0 solid;box-sizing:border-box;margin:0;padding:0}.custom-fields-component :host,.custom-fields-component html{-webkit-text-size-adjust:100%;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);line-height:1.5;tab-size:4;-webkit-tap-highlight-color:transparent}.custom-fields-component hr{border-top-width:1px;color:inherit;height:0}.custom-fields-component abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.custom-fields-component h1,.custom-fields-component h2,.custom-fields-component h3,.custom-fields-component h4,.custom-fields-component h5,.custom-fields-component h6{font-size:inherit;font-weight:inherit}.custom-fields-component a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.custom-fields-component b,.custom-fields-component strong{font-weight:bolder}.custom-fields-component code,.custom-fields-component kbd,.custom-fields-component pre,.custom-fields-component samp{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-size:1em;font-variation-settings:var(--default-mono-font-variation-settings,normal)}.custom-fields-component small{font-size:80%}.custom-fields-component sub,.custom-fields-component sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.custom-fields-component sub{bottom:-.25em}.custom-fields-component sup{top:-.5em}.custom-fields-component table{border-collapse:collapse;border-color:inherit;text-indent:0}.custom-fields-component :-moz-focusring{outline:auto}.custom-fields-component progress{vertical-align:baseline}.custom-fields-component summary{display:list-item}.custom-fields-component menu,.custom-fields-component ol,.custom-fields-component ul{list-style:none}.custom-fields-component audio,.custom-fields-component canvas,.custom-fields-component embed,.custom-fields-component iframe,.custom-fields-component img,.custom-fields-component object,.custom-fields-component svg,.custom-fields-component video{display:block;vertical-align:middle}.custom-fields-component img,.custom-fields-component video{height:auto;max-width:100%}.custom-fields-component button,.custom-fields-component input,.custom-fields-component optgroup,.custom-fields-component select,.custom-fields-component textarea{background-color:#0000;border-radius:0;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;opacity:1}.custom-fields-component ::file-selector-button{background-color:#0000;border-radius:0;color:inherit;font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;opacity:1}.custom-fields-component :where(select:is([multiple],[size])) optgroup{font-weight:bolder}.custom-fields-component :where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}.custom-fields-component ::file-selector-button{margin-inline-end:4px}.custom-fields-component ::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){.custom-fields-component ::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){.custom-fields-component ::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}.custom-fields-component textarea{resize:vertical}.custom-fields-component ::-webkit-search-decoration{-webkit-appearance:none}.custom-fields-component ::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}.custom-fields-component ::-webkit-datetime-edit{display:inline-flex}.custom-fields-component ::-webkit-datetime-edit-fields-wrapper{padding:0}.custom-fields-component ::-webkit-datetime-edit,.custom-fields-component ::-webkit-datetime-edit-year-field{padding-block:0}.custom-fields-component ::-webkit-datetime-edit-day-field,.custom-fields-component ::-webkit-datetime-edit-month-field{padding-block:0}.custom-fields-component ::-webkit-datetime-edit-hour-field,.custom-fields-component ::-webkit-datetime-edit-minute-field{padding-block:0}.custom-fields-component ::-webkit-datetime-edit-millisecond-field,.custom-fields-component ::-webkit-datetime-edit-second-field{padding-block:0}.custom-fields-component ::-webkit-datetime-edit-meridiem-field{padding-block:0}.custom-fields-component ::-webkit-calendar-picker-indicator{line-height:1}.custom-fields-component :-moz-ui-invalid{box-shadow:none}.custom-fields-component button,.custom-fields-component input:where([type=button],[type=reset],[type=submit]){appearance:button}.custom-fields-component ::file-selector-button{appearance:button}.custom-fields-component ::-webkit-inner-spin-button,.custom-fields-component ::-webkit-outer-spin-button{height:auto}.custom-fields-component [hidden]:where(:not([hidden=until-found])){display:none!important}.custom-fields-component [role=button]:not(:disabled),.custom-fields-component button:not(:disabled){cursor:pointer}.custom-fields-component :root.dark{color-scheme:dark}.custom-fields-component [data-field-wrapper]{scroll-margin-top:8rem}}@layer components{.custom-fields-component .tippy-box[data-animation=fade][data-state=hidden]{opacity:0}.custom-fields-component [data-tippy-root]{max-width:calc(100vw - 10px)}.custom-fields-component .tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.custom-fields-component .tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.custom-fields-component .tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:top}.custom-fields-component .tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.custom-fields-component .tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:bottom}.custom-fields-component .tippy-box[data-placement^=left]>.tippy-arrow{right:0}.custom-fields-component .tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:0}.custom-fields-component .tippy-box[data-placement^=right]>.tippy-arrow{left:0}.custom-fields-component .tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:100%}.custom-fields-component .tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.custom-fields-component .tippy-arrow{color:#333;height:16px;width:16px}.custom-fields-component .tippy-arrow:before{border-color:#0000;border-style:solid;content:"";position:absolute}.custom-fields-component .tippy-content{padding:5px 9px;position:relative;z-index:1}.custom-fields-component .tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.custom-fields-component .tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.custom-fields-component .tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.custom-fields-component .tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.custom-fields-component .tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.custom-fields-component .tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.custom-fields-component .tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.custom-fields-component .fi-avatar{border-radius:var(--radius-md);height:calc(var(--spacing)*8);object-fit:cover;object-position:center;width:calc(var(--spacing)*8)}.custom-fields-component .fi-avatar.fi-circular{border-radius:3.40282e+38px}.custom-fields-component .fi-avatar.fi-size-sm{height:calc(var(--spacing)*6);width:calc(var(--spacing)*6)}.custom-fields-component .fi-avatar.fi-size-lg{height:calc(var(--spacing)*10);width:calc(var(--spacing)*10)}.custom-fields-component .fi-badge{align-items:center;background-color:var(--gray-50);border-radius:var(--radius-md);column-gap:calc(var(--spacing)*1);font-size:var(--text-xs);justify-content:center;line-height:var(--tw-leading,var(--text-xs--line-height));min-width:1.5rem;padding-block:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*2);--tw-font-weight:var(--font-weight-medium);color:var(--gray-600);font-weight:var(--font-weight-medium);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-600);display:inline-flex}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge{--tw-ring-color:color-mix(in oklab,var(--gray-600)10%,transparent)}}.custom-fields-component .fi-badge{--tw-ring-inset:inset}.custom-fields-component .fi-badge:where(.dark,.dark *){background-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-400)10%,transparent)}}.custom-fields-component .fi-badge:where(.dark,.dark *){color:var(--gray-200);--tw-ring-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--gray-400)20%,transparent)}}.custom-fields-component .fi-badge:not(.fi-wrapped){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-fields-component .fi-badge.fi-disabled:not(.fi-force-enabled),.custom-fields-component .fi-badge[disabled]:not(.fi-force-enabled){cursor:default;opacity:.7}.custom-fields-component :is(.fi-badge.fi-disabled:not(.fi-force-enabled),.fi-badge[disabled]:not(.fi-force-enabled)):not([x-tooltip]){pointer-events:none}.custom-fields-component .fi-badge .fi-badge-label-ctn{display:grid}.custom-fields-component .fi-badge .fi-badge-label.fi-wrapped{text-wrap:wrap;word-break:break-word}.custom-fields-component .fi-badge .fi-badge-label:not(.fi-wrapped){overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-fields-component .fi-badge .fi-icon{flex-shrink:0}.custom-fields-component .fi-badge.fi-size-xs{min-width:1rem;padding-block:calc(var(--spacing)*0);padding-inline:calc(var(--spacing)*.5);--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.custom-fields-component .fi-badge.fi-size-sm{min-width:1.25rem;padding-block:calc(var(--spacing)*.5);padding-inline:calc(var(--spacing)*1.5);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.custom-fields-component .fi-badge.fi-color{background-color:var(--color-50);color:var(--text);--tw-ring-color:var(--color-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge.fi-color{--tw-ring-color:color-mix(in oklab,var(--color-600)10%,transparent)}}.custom-fields-component .fi-badge.fi-color:where(.dark,.dark *){background-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge.fi-color:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-400)10%,transparent)}}.custom-fields-component .fi-badge.fi-color:where(.dark,.dark *){color:var(--dark-text);--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge.fi-color:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-400)30%,transparent)}}.custom-fields-component .fi-badge.fi-color .fi-badge-delete-btn>.fi-icon{color:var(--color-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge.fi-color .fi-badge-delete-btn>.fi-icon{color:color-mix(in oklab,var(--color-700)50%,transparent)}}.custom-fields-component .fi-badge.fi-color .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *){color:var(--color-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge.fi-color .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *){color:color-mix(in oklab,var(--color-300)50%,transparent)}}.custom-fields-component .fi-badge:not(.fi-color) .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-badge:not(.fi-color) .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-badge .fi-badge-delete-btn{margin-block:calc(var(--spacing)*-1);padding:calc(var(--spacing)*1);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;--tw-outline-style:none;align-items:center;display:flex;justify-content:center;margin-inline-end:calc(var(--spacing)*-2);margin-inline-start:calc(var(--spacing)*-1);outline-style:none;transition-duration:75ms}.custom-fields-component .fi-badge .fi-badge-delete-btn>.fi-icon{color:var(--gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge .fi-badge-delete-btn>.fi-icon{color:color-mix(in oklab,var(--gray-700)50%,transparent)}}.custom-fields-component .fi-badge .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *){color:var(--gray-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *){color:color-mix(in oklab,var(--gray-300)50%,transparent)}}.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn>.fi-icon:focus-visible{color:var(--gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn>.fi-icon:focus-visible{color:color-mix(in oklab,var(--gray-700)75%,transparent)}}@media (hover:hover){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *):hover{color:var(--gray-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *):hover{color:color-mix(in oklab,var(--gray-300)75%,transparent)}}}.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *):focus-visible{color:var(--gray-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *):focus-visible{color:color-mix(in oklab,var(--gray-300)75%,transparent)}}@media (hover:hover){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn>.fi-icon:hover{color:var(--color-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn>.fi-icon:hover{color:color-mix(in oklab,var(--color-700)75%,transparent)}}}.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn>.fi-icon:focus-visible{color:var(--color-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn>.fi-icon:focus-visible{color:color-mix(in oklab,var(--color-700)75%,transparent)}}@media (hover:hover){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *):hover{color:var(--color-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *):hover{color:color-mix(in oklab,var(--color-300)75%,transparent)}}}.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *):focus-visible{color:var(--color-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn>.fi-icon:where(.dark,.dark *):focus-visible{color:color-mix(in oklab,var(--color-300)75%,transparent)}}.custom-fields-component .fi-breadcrumbs ol{align-items:center;column-gap:calc(var(--spacing)*2);display:flex;flex-wrap:wrap}.custom-fields-component .fi-breadcrumbs ol li{align-items:center;column-gap:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-500);display:flex;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-breadcrumbs ol li:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-breadcrumbs ol li a{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-breadcrumbs ol li a:hover{color:var(--gray-700)}.custom-fields-component .fi-breadcrumbs ol li a:where(.dark,.dark *):hover{color:var(--gray-200)}}.custom-fields-component .fi-breadcrumbs ol li .fi-icon{color:var(--gray-400);display:flex}.custom-fields-component .fi-breadcrumbs ol li .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-breadcrumbs ol li .fi-icon.fi-ltr:where(:dir(rtl),[dir=rtl],[dir=rtl] *),.custom-fields-component .fi-breadcrumbs ol li .fi-icon.fi-rtl:where(:dir(ltr),[dir=ltr],[dir=ltr] *){display:none}.custom-fields-component .fi-btn{align-items:center;border-radius:var(--radius-lg);font-size:var(--text-sm);gap:calc(var(--spacing)*1.5);justify-content:center;line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;--tw-outline-style:none;display:inline-grid;grid-auto-flow:column;outline-style:none;position:relative;transition-duration:75ms}.custom-fields-component :is(.fi-btn.fi-force-enabled,.fi-btn:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-btn.fi-disabled:not(.fi-force-enabled),.custom-fields-component .fi-btn[disabled]:not(.fi-force-enabled){cursor:default;opacity:.7}.custom-fields-component :is(.fi-btn.fi-disabled:not(.fi-force-enabled),.fi-btn[disabled]:not(.fi-force-enabled)):not([x-tooltip]){pointer-events:none}.custom-fields-component .fi-btn>.fi-icon{color:var(--gray-400);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-btn>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-btn.fi-size-xs{font-size:var(--text-xs);gap:calc(var(--spacing)*1);line-height:var(--tw-leading,var(--text-xs--line-height));padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*2)}.custom-fields-component .fi-btn.fi-size-sm{font-size:var(--text-sm);gap:calc(var(--spacing)*1);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*2.5)}.custom-fields-component .fi-btn.fi-size-lg{padding-block:calc(var(--spacing)*2.5);padding-inline:calc(var(--spacing)*3.5)}.custom-fields-component .fi-btn.fi-size-lg,.custom-fields-component .fi-btn.fi-size-xl{font-size:var(--text-sm);gap:calc(var(--spacing)*1.5);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-btn.fi-size-xl{padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4)}.custom-fields-component .fi-btn.fi-outlined{color:var(--gray-950);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-300)}.custom-fields-component .fi-btn.fi-outlined:where(.dark,.dark *){color:var(--color-white);--tw-ring-color:var(--gray-700)}@media (hover:hover){.custom-fields-component :is(.fi-btn.fi-outlined.fi-force-enabled,.fi-btn.fi-outlined:not(.fi-disabled):not([disabled])):hover{background-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-force-enabled,.fi-btn.fi-outlined:not(.fi-disabled):not([disabled])):hover{background-color:color-mix(in oklab,var(--gray-400)10%,transparent)}}}.custom-fields-component :is(.fi-btn.fi-outlined.fi-force-enabled,.fi-btn.fi-outlined:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-force-enabled,.fi-btn.fi-outlined:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:color-mix(in oklab,var(--gray-400)40%,transparent)}}.custom-fields-component .fi-btn.fi-outlined.fi-color{color:var(--text);--tw-ring-color:var(--color-600)}.custom-fields-component .fi-btn.fi-outlined.fi-color:where(.dark,.dark *){color:var(--dark-text);--tw-ring-color:var(--color-500)}@media (hover:hover){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):hover{background-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):hover{background-color:color-mix(in oklab,var(--color-500)10%,transparent)}}}.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:color-mix(in oklab,var(--color-500)40%,transparent)}}@media (hover:hover){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:var(--color-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-600)10%,transparent)}}}.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:color-mix(in oklab,var(--color-400)40%,transparent)}}.custom-fields-component .fi-btn.fi-outlined.fi-color>.fi-icon{color:var(--color-600)}.custom-fields-component .fi-btn.fi-outlined.fi-color>.fi-icon:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-btn:not(.fi-outlined){background-color:var(--color-white);color:var(--gray-950)}.custom-fields-component .fi-btn:not(.fi-outlined):where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-btn:not(.fi-outlined):where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-btn:not(.fi-outlined):where(.dark,.dark *){color:var(--color-white)}@media (hover:hover){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-force-enabled,.fi-btn:not(.fi-outlined):not(.fi-disabled):not([disabled])):hover{background-color:var(--gray-50)}.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-force-enabled,.fi-btn:not(.fi-outlined):not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-force-enabled,.fi-btn:not(.fi-outlined):not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)10%,transparent)}}}.custom-fields-component input:checked+label.fi-btn:not(.fi-outlined){background-color:var(--gray-400);color:var(--color-white);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component input:checked+label.fi-btn:not(.fi-outlined):where(.dark,.dark *){background-color:var(--gray-600)}@media (hover:hover){.custom-fields-component :is(input:checked+label.fi-btn:not(.fi-outlined).fi-force-enabled,input:checked+label.fi-btn:not(.fi-outlined):not(.fi-disabled):not([disabled])):hover{background-color:var(--gray-300)}.custom-fields-component :is(input:checked+label.fi-btn:not(.fi-outlined).fi-force-enabled,input:checked+label.fi-btn:not(.fi-outlined):not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:var(--gray-500)}}.custom-fields-component .fi-btn:not(.fi-outlined).fi-color:not(label){background-color:var(--bg);color:var(--text)}.custom-fields-component .fi-btn:not(.fi-outlined).fi-color:not(label):where(.dark,.dark *){background-color:var(--dark-bg);color:var(--dark-text)}@media (hover:hover){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):hover{background-color:var(--hover-bg);color:var(--hover-text)}}.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:color-mix(in oklab,var(--color-500)50%,transparent)}}@media (hover:hover){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:var(--dark-hover-bg);color:var(--dark-hover-text)}}.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:color-mix(in oklab,var(--color-400)50%,transparent)}}.custom-fields-component .fi-btn:not(.fi-outlined).fi-color:not(label)>.fi-icon{color:var(--text)}.custom-fields-component .fi-btn:not(.fi-outlined).fi-color:not(label)>.fi-icon:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component input:checked+:is(label.fi-btn:not(.fi-outlined).fi-color){background-color:var(--bg);color:var(--text);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component input:checked+:is(label.fi-btn:not(.fi-outlined).fi-color):where(.dark,.dark *){background-color:var(--dark-bg);color:var(--dark-text)}@media (hover:hover){.custom-fields-component input:checked+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):hover{background-color:var(--hover-bg);color:var(--hover-text)}.custom-fields-component input:checked+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:var(--dark-hover-bg);color:var(--dark-hover-text)}}.custom-fields-component input:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])){z-index:10;--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])){--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent)}}.custom-fields-component input:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component input:checked:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])){--tw-ring-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input:checked:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])){--tw-ring-color:color-mix(in oklab,var(--color-500)50%,transparent)}}.custom-fields-component input:checked:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *){--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input:checked:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-400)50%,transparent)}}.custom-fields-component label.fi-btn{cursor:pointer}.custom-fields-component label.fi-btn>.fi-icon:is(:checked+label>.fi-icon){color:var(--text)}.custom-fields-component label.fi-btn>.fi-icon:is(:checked+label>.fi-icon):where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-btn:not(.fi-color),.custom-fields-component label.fi-btn{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-btn:not(.fi-color),.custom-fields-component label.fi-btn{--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent)}}.custom-fields-component :is(.fi-btn:not(.fi-color),label.fi-btn):where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn:not(.fi-color),label.fi-btn):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-btn.fi-labeled-from-2xl,.custom-fields-component .fi-btn.fi-labeled-from-lg,.custom-fields-component .fi-btn.fi-labeled-from-md,.custom-fields-component .fi-btn.fi-labeled-from-sm,.custom-fields-component .fi-btn.fi-labeled-from-xl{display:none}@media (min-width:40rem){.custom-fields-component .fi-btn.fi-labeled-from-sm{display:inline-grid}}@media (min-width:48rem){.custom-fields-component .fi-btn.fi-labeled-from-md{display:inline-grid}}@media (min-width:64rem){.custom-fields-component .fi-btn.fi-labeled-from-lg{display:inline-grid}}@media (min-width:80rem){.custom-fields-component .fi-btn.fi-labeled-from-xl{display:inline-grid}}@media (min-width:96rem){.custom-fields-component .fi-btn.fi-labeled-from-2xl{display:inline-grid}}.custom-fields-component .fi-btn .fi-btn-badge-ctn{inset-inline-start:100%;top:calc(var(--spacing)*0);z-index:1;--tw-translate-x:-50%;--tw-translate-y:-50%;background-color:var(--color-white);border-radius:var(--radius-md);display:flex;position:absolute;translate:var(--tw-translate-x)var(--tw-translate-y);width:max-content}.custom-fields-component .fi-btn .fi-btn-badge-ctn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-btn .fi-btn-badge-ctn:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-btn-group{border-radius:var(--radius-lg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);display:grid;grid-auto-flow:column}.custom-fields-component .fi-btn-group:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-btn-group:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-btn-group>.fi-btn{border-radius:0;flex:1}.custom-fields-component .fi-btn-group>.fi-btn:nth-child(1 of .fi-btn){border-end-start-radius:var(--radius-lg);border-start-start-radius:var(--radius-lg)}.custom-fields-component .fi-btn-group>.fi-btn:nth-last-child(1 of .fi-btn){border-end-end-radius:var(--radius-lg);border-start-end-radius:var(--radius-lg)}.custom-fields-component .fi-btn-group>.fi-btn:not(:nth-child(1 of .fi-btn)){--tw-shadow:-1px 0 0 0 var(--tw-shadow-color,var(--color-gray-200));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-btn-group>.fi-btn:not(:nth-child(1 of .fi-btn)):where(.dark,.dark *){--tw-shadow:-1px 0 0 0 var(--tw-shadow-color,#fff3);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-btn-group>.fi-btn:not(:nth-last-child(1 of .fi-btn)){margin-inline-end:1px}.custom-fields-component .fi-btn-group>.fi-btn.fi-processing:enabled{cursor:wait;opacity:.7}.custom-fields-component .fi-btn-group>.fi-btn:not(.fi-outlined){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-btn-group>.fi-btn:not(.fi-color),.custom-fields-component label:is(.fi-btn-group>.fi-btn){--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-dropdown-header{font-size:var(--text-sm);gap:calc(var(--spacing)*2);line-height:var(--tw-leading,var(--text-sm--line-height));padding:calc(var(--spacing)*3);width:100%;--tw-font-weight:var(--font-weight-medium);display:flex;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-dropdown-header .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-dropdown-header .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-dropdown-header span{color:var(--gray-700);flex:1;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap}.custom-fields-component .fi-dropdown-header span:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-dropdown-header.fi-color .fi-icon{color:var(--color-500)}.custom-fields-component .fi-dropdown-header.fi-color .fi-icon:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-dropdown-header.fi-color span{color:var(--text)}.custom-fields-component .fi-dropdown-header.fi-color span:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component :scope .fi-dropdown-trigger{cursor:pointer;display:flex}.custom-fields-component :scope .fi-dropdown-panel{background-color:var(--color-white);border-radius:var(--radius-lg);z-index:20;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);width:100vw;--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent);max-width:14rem!important;position:absolute;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.custom-fields-component :scope .fi-dropdown-panel:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :scope .fi-dropdown-panel:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component :where(:scope .fi-dropdown-panel:not(.fi-dropdown-list)>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-100);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(:scope .fi-dropdown-panel:not(.fi-dropdown-list):where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(:scope .fi-dropdown-panel:not(.fi-dropdown-list):where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component :scope .fi-dropdown-panel.fi-opacity-0{opacity:0}.custom-fields-component :scope .fi-dropdown-panel.fi-width-xs{max-width:var(--container-xs)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-sm{max-width:var(--container-sm)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-md{max-width:var(--container-md)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-lg{max-width:var(--container-lg)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-xl{max-width:var(--container-xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-2xl{max-width:var(--container-2xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-3xl{max-width:var(--container-3xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-4xl{max-width:var(--container-4xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-5xl{max-width:var(--container-5xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-6xl{max-width:var(--container-6xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-7xl{max-width:var(--container-7xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-scrollable{overflow-y:auto}.custom-fields-component .fi-dropdown-list{display:grid;gap:1px;padding:calc(var(--spacing)*1)}.custom-fields-component .fi-dropdown-list>.fi-grid{overflow-x:hidden}.custom-fields-component .fi-dropdown-list-item{align-items:center;border-radius:var(--radius-md);font-size:var(--text-sm);gap:calc(var(--spacing)*2);line-height:var(--tw-leading,var(--text-sm--line-height));padding:calc(var(--spacing)*2);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));white-space:nowrap;width:100%;--tw-duration:75ms;--tw-outline-style:none;display:flex;outline-style:none;overflow:hidden;transition-duration:75ms;-webkit-user-select:none;user-select:none}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):hover{background-color:var(--gray-50)}}.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):focus-visible{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]).fi-selected{background-color:var(--gray-50)}.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]).fi-selected:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]).fi-selected:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-dropdown-list-item.fi-disabled,.custom-fields-component .fi-dropdown-list-item[disabled]{cursor:default;opacity:.7}.custom-fields-component :is(.fi-dropdown-list-item.fi-disabled,.fi-dropdown-list-item[disabled]):not([x-tooltip]){pointer-events:none}.custom-fields-component .fi-dropdown-list-item .fi-icon{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-dropdown-list-item .fi-dropdown-list-item-image{background-position:50%;background-size:cover;border-radius:3.40282e+38px;height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}.custom-fields-component .fi-dropdown-list-item>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-dropdown-list-item>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-dropdown-list-item>.fi-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-dropdown-list-item>.fi-icon.fi-color:where(.dark,.dark *){color:var(--color-400)}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):hover{background-color:var(--color-50)}}.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):focus-visible{background-color:var(--color-50)}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):where(.dark,.dark *):hover{background-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-400)10%,transparent)}}}.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):where(.dark,.dark *):focus-visible{background-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-400)10%,transparent)}}.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]).fi-selected{background-color:var(--color-50)}.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]).fi-selected:where(.dark,.dark *){background-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]).fi-selected:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-400)10%,transparent)}}.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label{color:var(--text)}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label:hover{color:var(--hover-text)}}.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label:where(.dark,.dark *){color:var(--dark-text)}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label:where(.dark,.dark *):hover{color:var(--dark-hover-text)}}.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label.fi-selected{color:var(--hover-text)}.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label.fi-selected:where(.dark,.dark *){color:var(--dark-hover-text)}.custom-fields-component .fi-dropdown-list-item .fi-badge{min-width:1.25rem;padding-block:calc(var(--spacing)*.5);padding-inline:calc(var(--spacing)*1.5);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.custom-fields-component .fi-dropdown-list-item-label{color:var(--gray-700);flex:1;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap}.custom-fields-component .fi-dropdown-list-item-label:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-empty-state{padding-block:calc(var(--spacing)*12);padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-empty-state:not(.fi-empty-state-not-contained){background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component .fi-empty-state:not(.fi-empty-state-not-contained):where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-empty-state:not(.fi-empty-state-not-contained):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-empty-state .fi-empty-state-content{display:grid;justify-items:center;margin-inline:auto;max-width:var(--container-lg);text-align:center}.custom-fields-component .fi-empty-state .fi-empty-state-text-ctn{display:grid;justify-items:center;text-align:center}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg{background-color:var(--gray-100);border-radius:3.40282e+38px;margin-bottom:calc(var(--spacing)*4);padding:calc(var(--spacing)*3)}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg:where(.dark,.dark *){background-color:var(--gray-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-500)20%,transparent)}}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg.fi-color{background-color:var(--color-100)}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg.fi-color:where(.dark,.dark *){background-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg.fi-color:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-500)20%,transparent)}}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg .fi-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg .fi-icon.fi-color:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-empty-state .fi-empty-state-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-empty-state .fi-empty-state-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-empty-state .fi-empty-state-description{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));margin-top:calc(var(--spacing)*1)}.custom-fields-component .fi-empty-state .fi-empty-state-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-empty-state .fi-empty-state-footer{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-empty-state.fi-compact{padding-block:calc(var(--spacing)*6)}.custom-fields-component .fi-empty-state.fi-compact .fi-empty-state-content{align-items:flex-start;display:flex;gap:calc(var(--spacing)*4);margin-inline:calc(var(--spacing)*0);max-width:none;text-align:start}.custom-fields-component .fi-empty-state.fi-compact .fi-empty-state-icon-bg{flex-shrink:0;margin-bottom:calc(var(--spacing)*0)}.custom-fields-component .fi-empty-state.fi-compact .fi-empty-state-text-ctn{flex:1;justify-items:start;text-align:start}.custom-fields-component .fi-empty-state.fi-compact .fi-empty-state-description{margin-top:calc(var(--spacing)*1)}.custom-fields-component .fi-empty-state.fi-compact .fi-empty-state-footer{margin-top:calc(var(--spacing)*4)}.custom-fields-component .fi-fieldset>legend{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-inline:calc(var(--spacing)*2);--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium);margin-inline-start:calc(var(--spacing)*-2)}.custom-fields-component .fi-fieldset>legend:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fieldset>legend .fi-fieldset-label-required-mark{--tw-font-weight:var(--font-weight-medium);color:var(--danger-600);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fieldset>legend .fi-fieldset-label-required-mark:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-fieldset.fi-fieldset-label-hidden>legend{border-width:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.custom-fields-component .fi-fieldset:not(.fi-fieldset-not-contained){border-color:var(--gray-200);border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;padding:calc(var(--spacing)*6)}.custom-fields-component .fi-fieldset:not(.fi-fieldset-not-contained):where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fieldset:not(.fi-fieldset-not-contained):where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fieldset.fi-fieldset-not-contained{padding-top:calc(var(--spacing)*6)}.custom-fields-component .fi-grid:not(.fi-grid-direction-col){display:grid;grid-template-columns:var(--cols-default)}@media (min-width:40rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).sm\:fi-grid-cols{grid-template-columns:var(--cols-sm)}}@media (min-width:48rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).md\:fi-grid-cols{grid-template-columns:var(--cols-md)}}@media (min-width:64rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).lg\:fi-grid-cols{grid-template-columns:var(--cols-lg)}}@media (min-width:80rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).xl\:fi-grid-cols{grid-template-columns:var(--cols-xl)}}@media (min-width:96rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\32 xl\:fi-grid-cols{grid-template-columns:var(--cols-2xl)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@3xs\:fi-grid-cols{grid-template-columns:var(--cols-c3xs)}}@container (min-width:18rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@2xs\:fi-grid-cols{grid-template-columns:var(--cols-c2xs)}}@container (min-width:20rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@xs\:fi-grid-cols{grid-template-columns:var(--cols-cxs)}}@container (min-width:24rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@sm\:fi-grid-cols{grid-template-columns:var(--cols-csm)}}@container (min-width:28rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@md\:fi-grid-cols{grid-template-columns:var(--cols-cmd)}}@container (min-width:32rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@lg\:fi-grid-cols{grid-template-columns:var(--cols-clg)}}@container (min-width:36rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@xl\:fi-grid-cols{grid-template-columns:var(--cols-cxl)}}@container (min-width:42rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@2xl\:fi-grid-cols{grid-template-columns:var(--cols-c2xl)}}@container (min-width:48rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@3xl\:fi-grid-cols{grid-template-columns:var(--cols-c3xl)}}@container (min-width:56rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@4xl\:fi-grid-cols{grid-template-columns:var(--cols-c4xl)}}@container (min-width:64rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@5xl\:fi-grid-cols{grid-template-columns:var(--cols-c5xl)}}@container (min-width:72rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@6xl\:fi-grid-cols{grid-template-columns:var(--cols-c6xl)}}@container (min-width:80rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@7xl\:fi-grid-cols{grid-template-columns:var(--cols-c7xl)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\!\@sm\:fi-grid-cols{grid-template-columns:var(--cols-ncsm)}}@media (min-width:48rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\!\@md\:fi-grid-cols{grid-template-columns:var(--cols-ncmd)}}@media (min-width:64rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\!\@lg\:fi-grid-cols{grid-template-columns:var(--cols-nclg)}}@media (min-width:80rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\!\@xl\:fi-grid-cols{grid-template-columns:var(--cols-ncxl)}}@media (min-width:96rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\!\@2xl\:fi-grid-cols{grid-template-columns:var(--cols-nc2xl)}}}.custom-fields-component .fi-grid.fi-grid-direction-col{columns:var(--cols-default)}@media (min-width:40rem){.custom-fields-component .fi-grid.fi-grid-direction-col.sm\:fi-grid-cols{columns:var(--cols-sm)}}@media (min-width:48rem){.custom-fields-component .fi-grid.fi-grid-direction-col.md\:fi-grid-cols{columns:var(--cols-md)}}@media (min-width:64rem){.custom-fields-component .fi-grid.fi-grid-direction-col.lg\:fi-grid-cols{columns:var(--cols-lg)}}@media (min-width:80rem){.custom-fields-component .fi-grid.fi-grid-direction-col.xl\:fi-grid-cols{columns:var(--cols-xl)}}@media (min-width:96rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\32 xl\:fi-grid-cols{columns:var(--cols-2xl)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@3xs\:fi-grid-cols{columns:var(--cols-c3xs)}}@container (min-width:18rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@2xs\:fi-grid-cols{columns:var(--cols-c2xs)}}@container (min-width:20rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@xs\:fi-grid-cols{columns:var(--cols-cxs)}}@container (min-width:24rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@sm\:fi-grid-cols{columns:var(--cols-csm)}}@container (min-width:28rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@md\:fi-grid-cols{columns:var(--cols-cmd)}}@container (min-width:32rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@lg\:fi-grid-cols{columns:var(--cols-clg)}}@container (min-width:36rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@xl\:fi-grid-cols{columns:var(--cols-cxl)}}@container (min-width:42rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@2xl\:fi-grid-cols{columns:var(--cols-c2xl)}}@container (min-width:48rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@3xl\:fi-grid-cols{columns:var(--cols-c3xl)}}@container (min-width:56rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@4xl\:fi-grid-cols{columns:var(--cols-c4xl)}}@container (min-width:64rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@5xl\:fi-grid-cols{columns:var(--cols-c5xl)}}@container (min-width:72rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@6xl\:fi-grid-cols{columns:var(--cols-c6xl)}}@container (min-width:80rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@7xl\:fi-grid-cols{columns:var(--cols-c7xl)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\!\@sm\:fi-grid-cols{columns:var(--cols-ncsm)}}@media (min-width:48rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\!\@md\:fi-grid-cols{columns:var(--cols-ncmd)}}@media (min-width:64rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\!\@lg\:fi-grid-cols{columns:var(--cols-nclg)}}@media (min-width:80rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\!\@xl\:fi-grid-cols{columns:var(--cols-ncxl)}}@media (min-width:96rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\!\@2xl\:fi-grid-cols{columns:var(--cols-nc2xl)}}}@supports (container-type:inline-size){.custom-fields-component .fi-grid-ctn{container-type:inline-size}}.custom-fields-component .fi-grid-col{grid-column:var(--col-span-default)}@media (min-width:40rem){.custom-fields-component .fi-grid-col.sm\:fi-grid-col-span{grid-column:var(--col-span-sm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.md\:fi-grid-col-span{grid-column:var(--col-span-md)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.lg\:fi-grid-col-span{grid-column:var(--col-span-lg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.xl\:fi-grid-col-span{grid-column:var(--col-span-xl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\32 xl\:fi-grid-col-span{grid-column:var(--col-span-2xl)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component .fi-grid-col.\@3xs\:fi-grid-col-span{grid-column:var(--col-span-c3xs)}}@container (min-width:18rem){.custom-fields-component .fi-grid-col.\@2xs\:fi-grid-col-span{grid-column:var(--col-span-c2xs)}}@container (min-width:20rem){.custom-fields-component .fi-grid-col.\@xs\:fi-grid-col-span{grid-column:var(--col-span-cxs)}}@container (min-width:24rem){.custom-fields-component .fi-grid-col.\@sm\:fi-grid-col-span{grid-column:var(--col-span-csm)}}@container (min-width:28rem){.custom-fields-component .fi-grid-col.\@md\:fi-grid-col-span{grid-column:var(--col-span-cmd)}}@container (min-width:32rem){.custom-fields-component .fi-grid-col.\@lg\:fi-grid-col-span{grid-column:var(--col-span-clg)}}@container (min-width:36rem){.custom-fields-component .fi-grid-col.\@xl\:fi-grid-col-span{grid-column:var(--col-span-cxl)}}@container (min-width:42rem){.custom-fields-component .fi-grid-col.\@2xl\:fi-grid-col-span{grid-column:var(--col-span-c2xl)}}@container (min-width:48rem){.custom-fields-component .fi-grid-col.\@3xl\:fi-grid-col-span{grid-column:var(--col-span-c3xl)}}@container (min-width:56rem){.custom-fields-component .fi-grid-col.\@4xl\:fi-grid-col-span{grid-column:var(--col-span-c4xl)}}@container (min-width:64rem){.custom-fields-component .fi-grid-col.\@5xl\:fi-grid-col-span{grid-column:var(--col-span-c5xl)}}@container (min-width:72rem){.custom-fields-component .fi-grid-col.\@6xl\:fi-grid-col-span{grid-column:var(--col-span-c6xl)}}@container (min-width:80rem){.custom-fields-component .fi-grid-col.\@7xl\:fi-grid-col-span{grid-column:var(--col-span-c7xl)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-grid-col.\!\@sm\:fi-grid-col-span{grid-column:var(--col-span-ncsm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.\!\@md\:fi-grid-col-span{grid-column:var(--col-span-ncmd)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.\!\@lg\:fi-grid-col-span{grid-column:var(--col-span-nclg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.\!\@xl\:fi-grid-col-span{grid-column:var(--col-span-ncxl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\!\@2xl\:fi-grid-col-span{grid-column:var(--col-span-nc2xl)}}}.custom-fields-component .fi-grid-col.fi-grid-col-start{grid-column-start:var(--col-start-default)}@media (min-width:40rem){.custom-fields-component .fi-grid-col.sm\:fi-grid-col-start{grid-column-start:var(--col-start-sm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.md\:fi-grid-col-start{grid-column-start:var(--col-start-md)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.lg\:fi-grid-col-start{grid-column-start:var(--col-start-lg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.xl\:fi-grid-col-start{grid-column-start:var(--col-start-xl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\32 xl\:fi-grid-col-start{grid-column-start:var(--col-start-2xl)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component .fi-grid-col.\@3xs\:fi-grid-col-start{grid-column-start:var(--col-start-c3xs)}}@container (min-width:18rem){.custom-fields-component .fi-grid-col.\@2xs\:fi-grid-col-start{grid-column-start:var(--col-start-c2xs)}}@container (min-width:20rem){.custom-fields-component .fi-grid-col.\@xs\:fi-grid-col-start{grid-column-start:var(--col-start-cxs)}}@container (min-width:24rem){.custom-fields-component .fi-grid-col.\@sm\:fi-grid-col-start{grid-column-start:var(--col-start-csm)}}@container (min-width:28rem){.custom-fields-component .fi-grid-col.\@md\:fi-grid-col-start{grid-column-start:var(--col-start-cmd)}}@container (min-width:32rem){.custom-fields-component .fi-grid-col.\@lg\:fi-grid-col-start{grid-column-start:var(--col-start-clg)}}@container (min-width:36rem){.custom-fields-component .fi-grid-col.\@xl\:fi-grid-col-start{grid-column-start:var(--col-start-cxl)}}@container (min-width:42rem){.custom-fields-component .fi-grid-col.\@2xl\:fi-grid-col-start{grid-column-start:var(--col-start-c2xl)}}@container (min-width:48rem){.custom-fields-component .fi-grid-col.\@3xl\:fi-grid-col-start{grid-column-start:var(--col-start-c3xl)}}@container (min-width:56rem){.custom-fields-component .fi-grid-col.\@4xl\:fi-grid-col-start{grid-column-start:var(--col-start-c4xl)}}@container (min-width:64rem){.custom-fields-component .fi-grid-col.\@5xl\:fi-grid-col-start{grid-column-start:var(--col-start-c5xl)}}@container (min-width:72rem){.custom-fields-component .fi-grid-col.\@6xl\:fi-grid-col-start{grid-column-start:var(--col-start-c6xl)}}@container (min-width:80rem){.custom-fields-component .fi-grid-col.\@7xl\:fi-grid-col-start{grid-column-start:var(--col-start-c7xl)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-grid-col.\!\@sm\:fi-grid-col-start{grid-column-start:var(--col-start-ncsm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.\!\@md\:fi-grid-col-start{grid-column-start:var(--col-start-ncmd)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.\!\@lg\:fi-grid-col-start{grid-column-start:var(--col-start-nclg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.\!\@xl\:fi-grid-col-start{grid-column-start:var(--col-start-ncxl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\!\@2xl\:fi-grid-col-start{grid-column-start:var(--col-start-nc2xl)}}}.custom-fields-component .fi-grid-col.fi-grid-col-order{order:var(--col-order-default)}@media (min-width:40rem){.custom-fields-component .fi-grid-col.sm\:fi-grid-col-order{order:var(--col-order-sm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.md\:fi-grid-col-order{order:var(--col-order-md)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.lg\:fi-grid-col-order{order:var(--col-order-lg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.xl\:fi-grid-col-order{order:var(--col-order-xl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\32 xl\:fi-grid-col-order{order:var(--col-order-2xl)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component .fi-grid-col.\@3xs\:fi-grid-col-order{order:var(--col-order-c3xs)}}@container (min-width:18rem){.custom-fields-component .fi-grid-col.\@2xs\:fi-grid-col-order{order:var(--col-order-c2xs)}}@container (min-width:20rem){.custom-fields-component .fi-grid-col.\@xs\:fi-grid-col-order{order:var(--col-order-cxs)}}@container (min-width:24rem){.custom-fields-component .fi-grid-col.\@sm\:fi-grid-col-order{order:var(--col-order-csm)}}@container (min-width:28rem){.custom-fields-component .fi-grid-col.\@md\:fi-grid-col-order{order:var(--col-order-cmd)}}@container (min-width:32rem){.custom-fields-component .fi-grid-col.\@lg\:fi-grid-col-order{order:var(--col-order-clg)}}@container (min-width:36rem){.custom-fields-component .fi-grid-col.\@xl\:fi-grid-col-order{order:var(--col-order-cxl)}}@container (min-width:42rem){.custom-fields-component .fi-grid-col.\@2xl\:fi-grid-col-order{order:var(--col-order-c2xl)}}@container (min-width:48rem){.custom-fields-component .fi-grid-col.\@3xl\:fi-grid-col-order{order:var(--col-order-c3xl)}}@container (min-width:56rem){.custom-fields-component .fi-grid-col.\@4xl\:fi-grid-col-order{order:var(--col-order-c4xl)}}@container (min-width:64rem){.custom-fields-component .fi-grid-col.\@5xl\:fi-grid-col-order{order:var(--col-order-c5xl)}}@container (min-width:72rem){.custom-fields-component .fi-grid-col.\@6xl\:fi-grid-col-order{order:var(--col-order-c6xl)}}@container (min-width:80rem){.custom-fields-component .fi-grid-col.\@7xl\:fi-grid-col-order{order:var(--col-order-c7xl)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-grid-col.\!\@sm\:fi-grid-col-order{order:var(--col-order-ncsm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.\!\@md\:fi-grid-col-order{order:var(--col-order-ncmd)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.\!\@lg\:fi-grid-col-order{order:var(--col-order-nclg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.\!\@xl\:fi-grid-col-order{order:var(--col-order-ncxl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\!\@2xl\:fi-grid-col-order{order:var(--col-order-nc2xl)}}}.custom-fields-component .fi-grid-col.fi-hidden{display:none}.custom-fields-component .fi-icon{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}.custom-fields-component .fi-icon.fi-size-xs{height:calc(var(--spacing)*3);width:calc(var(--spacing)*3)}.custom-fields-component .fi-icon.fi-size-sm{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4)}.custom-fields-component .fi-icon.fi-size-md{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}.custom-fields-component .fi-icon.fi-size-lg{height:calc(var(--spacing)*6);width:calc(var(--spacing)*6)}.custom-fields-component .fi-icon.fi-size-xl{height:calc(var(--spacing)*7);width:calc(var(--spacing)*7)}.custom-fields-component .fi-icon.fi-size-2xl{height:calc(var(--spacing)*8);width:calc(var(--spacing)*8)}.custom-fields-component .fi-icon>svg{height:inherit;width:inherit}.custom-fields-component .fi-icon-btn{border-radius:var(--radius-lg);color:var(--gray-500);height:calc(var(--spacing)*9);margin:calc(var(--spacing)*-2);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:calc(var(--spacing)*9);--tw-duration:75ms;--tw-outline-style:none;align-items:center;display:flex;justify-content:center;outline-style:none;position:relative;transition-duration:75ms}.custom-fields-component .fi-icon-btn:where(.dark,.dark *){color:var(--gray-500)}@media (hover:hover){.custom-fields-component :is(.fi-icon-btn.fi-force-enabled,.fi-icon-btn:not(.fi-disabled):not([disabled])):hover{color:var(--gray-600)}}.custom-fields-component :is(.fi-icon-btn.fi-force-enabled,.fi-icon-btn:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}@media (hover:hover){.custom-fields-component :is(.fi-icon-btn.fi-force-enabled,.fi-icon-btn:not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{color:var(--gray-400)}}.custom-fields-component :is(.fi-icon-btn.fi-force-enabled,.fi-icon-btn:not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-icon-btn.fi-disabled:not(.fi-force-enabled),.custom-fields-component .fi-icon-btn[disabled]:not(.fi-force-enabled){cursor:default;opacity:.7}.custom-fields-component :is(.fi-icon-btn.fi-disabled:not(.fi-force-enabled),.fi-icon-btn[disabled]:not(.fi-force-enabled)):not([x-tooltip]){pointer-events:none}.custom-fields-component .fi-icon-btn.fi-size-xs{height:calc(var(--spacing)*7);width:calc(var(--spacing)*7)}.custom-fields-component .fi-icon-btn.fi-size-xs:has(.fi-icon.fi-size-sm){margin:calc(var(--spacing)*-1.5)}.custom-fields-component .fi-icon-btn.fi-size-xs:has(.fi-icon.fi-size-md){margin:calc(var(--spacing)*-1)}.custom-fields-component .fi-icon-btn.fi-size-xs:has(.fi-icon.fi-size-lg){margin:calc(var(--spacing)*-.5)}.custom-fields-component .fi-icon-btn.fi-size-sm{height:calc(var(--spacing)*8);width:calc(var(--spacing)*8)}.custom-fields-component .fi-icon-btn.fi-size-sm:has(.fi-icon.fi-size-sm){margin:calc(var(--spacing)*-2)}.custom-fields-component .fi-icon-btn.fi-size-sm:has(.fi-icon.fi-size-md){margin:calc(var(--spacing)*-1.5)}.custom-fields-component .fi-icon-btn.fi-size-sm:has(.fi-icon.fi-size-lg){margin:calc(var(--spacing)*-1)}.custom-fields-component .fi-icon-btn.fi-size-md:has(.fi-icon.fi-size-sm){margin:calc(var(--spacing)*-2.5)}.custom-fields-component .fi-icon-btn.fi-size-md:has(.fi-icon.fi-size-lg){margin:calc(var(--spacing)*-1.5)}.custom-fields-component .fi-icon-btn.fi-size-lg{height:calc(var(--spacing)*10);width:calc(var(--spacing)*10)}.custom-fields-component .fi-icon-btn.fi-size-lg:has(.fi-icon.fi-size-sm){margin:calc(var(--spacing)*-3)}.custom-fields-component .fi-icon-btn.fi-size-lg:has(.fi-icon.fi-size-md){margin:calc(var(--spacing)*-2.5)}.custom-fields-component .fi-icon-btn.fi-size-lg:has(.fi-icon.fi-size-lg){margin:calc(var(--spacing)*-2)}.custom-fields-component .fi-icon-btn.fi-size-xl{height:calc(var(--spacing)*11);width:calc(var(--spacing)*11)}.custom-fields-component .fi-icon-btn.fi-size-xl:has(.fi-icon.fi-size-sm){margin:calc(var(--spacing)*-3.5)}.custom-fields-component .fi-icon-btn.fi-size-xl:has(.fi-icon.fi-size-md){margin:calc(var(--spacing)*-3)}.custom-fields-component .fi-icon-btn.fi-size-xl:has(.fi-icon.fi-size-lg){margin:calc(var(--spacing)*-2.5)}.custom-fields-component .fi-icon-btn.fi-color{color:var(--text)}.custom-fields-component .fi-icon-btn.fi-color:where(.dark,.dark *){color:var(--dark-text)}@media (hover:hover){.custom-fields-component :is(.fi-icon-btn.fi-color.fi-force-enabled,.fi-icon-btn.fi-color:not(.fi-disabled):not([disabled])):hover{color:var(--hover-text)}}.custom-fields-component :is(.fi-icon-btn.fi-color.fi-force-enabled,.fi-icon-btn.fi-color:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:var(--color-600)}@media (hover:hover){.custom-fields-component :is(.fi-icon-btn.fi-color.fi-force-enabled,.fi-icon-btn.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{color:var(--dark-hover-text)}}.custom-fields-component :is(.fi-icon-btn.fi-color.fi-force-enabled,.fi-icon-btn.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:var(--color-500)}.custom-fields-component .fi-icon-btn>.fi-icon-btn-badge-ctn{inset-inline-start:100%;top:calc(var(--spacing)*1);z-index:1;--tw-translate-x:-50%;--tw-translate-y:-50%;background-color:var(--color-white);border-radius:var(--radius-md);display:flex;position:absolute;translate:var(--tw-translate-x)var(--tw-translate-y);width:max-content}.custom-fields-component .fi-icon-btn>.fi-icon-btn-badge-ctn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-icon-btn>.fi-icon-btn-badge-ctn:where(.dark,.dark *){background-color:var(--gray-900)}@media (min-width:40rem){.custom-fields-component .fi-icon-btn:has(+.fi-btn.fi-labeled-from-sm){display:none}}@media (min-width:48rem){.custom-fields-component .fi-icon-btn:has(+.fi-btn.fi-labeled-from-md){display:none}}@media (min-width:64rem){.custom-fields-component .fi-icon-btn:has(+.fi-btn.fi-labeled-from-lg){display:none}}@media (min-width:80rem){.custom-fields-component .fi-icon-btn:has(+.fi-btn.fi-labeled-from-xl){display:none}}@media (min-width:96rem){.custom-fields-component .fi-icon-btn:has(+.fi-btn.fi-labeled-from-2xl){display:none}}.custom-fields-component input[type=checkbox].fi-checkbox-input{appearance:none;height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);--tw-border-style:none;background-color:var(--color-white);color:var(--primary-600);vertical-align:middle;--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);border-radius:.25rem;border-style:none}.custom-fields-component input[type=checkbox].fi-checkbox-input:checked{background-color:var(--primary-600);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component input[type=checkbox].fi-checkbox-input:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600);--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.custom-fields-component input[type=checkbox].fi-checkbox-input:checked:focus{--tw-ring-color:var(--primary-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:checked:focus{--tw-ring-color:color-mix(in oklab,var(--primary-500)50%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:disabled{background-color:var(--gray-50);color:var(--gray-50);pointer-events:none}.custom-fields-component input[type=checkbox].fi-checkbox-input:disabled:checked{background-color:var(--gray-400);color:var(--gray-400)}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *){color:var(--primary-500);--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):checked{background-color:var(--primary-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):focus{--tw-ring-color:var(--primary-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):checked:focus{--tw-ring-color:var(--primary-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):checked:focus{--tw-ring-color:color-mix(in oklab,var(--primary-400)50%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):disabled{--tw-ring-color:#ffffff1a;background-color:#0000}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):disabled{--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):disabled:checked{background-color:var(--gray-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0'/%3E%3C/svg%3E")}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate{background-color:var(--primary-600);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:where(.dark,.dark *){background-color:var(--primary-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M4.5 6.75a1.25 1.25 0 0 0 0 2.5h7a1.25 1.25 0 0 0 0-2.5z'/%3E%3C/svg%3E")}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:focus{--tw-ring-color:color-mix(in oklab,var(--primary-500)50%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:focus:where(.dark,.dark *){--tw-ring-color:var(--primary-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:focus:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--primary-400)50%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:disabled{background-color:var(--gray-400)}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:disabled:where(.dark,.dark *){background-color:var(--gray-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid{color:var(--danger-600);--tw-ring-color:var(--danger-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:checked{background-color:var(--danger-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:focus{--tw-ring-color:var(--danger-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:checked:focus{--tw-ring-color:var(--danger-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:checked:focus{--tw-ring-color:color-mix(in oklab,var(--danger-500)50%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:where(.dark,.dark *){color:var(--danger-500);--tw-ring-color:var(--danger-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:where(.dark,.dark *):checked{background-color:var(--danger-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:where(.dark,.dark *):focus{--tw-ring-color:var(--danger-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:where(.dark,.dark *):checked:focus{--tw-ring-color:var(--danger-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:where(.dark,.dark *):checked:focus{--tw-ring-color:color-mix(in oklab,var(--danger-400)50%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate{background-color:var(--danger-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate:where(.dark,.dark *){background-color:var(--danger-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate:focus{--tw-ring-color:var(--danger-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate:focus{--tw-ring-color:color-mix(in oklab,var(--danger-500)50%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate:focus:where(.dark,.dark *){--tw-ring-color:var(--danger-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate:focus:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--danger-400)50%,transparent)}}.custom-fields-component input.fi-input{appearance:none;--tw-border-style:none;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*3);text-align:start;width:100%;--tw-leading:calc(var(--spacing)*6);color:var(--gray-950);line-height:calc(var(--spacing)*6);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;background-color:#0000;border-style:none;display:block;transition-duration:75ms}.custom-fields-component input.fi-input::placeholder{color:var(--gray-400)}.custom-fields-component input.fi-input:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}.custom-fields-component input.fi-input:disabled{color:var(--gray-500);-webkit-text-fill-color:var(--color-gray-500)}.custom-fields-component input.fi-input:disabled::placeholder{-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component input.fi-input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component input.fi-input:where(.dark,.dark *)::placeholder{color:var(--gray-500)}.custom-fields-component input.fi-input:where(.dark,.dark *):disabled{color:var(--gray-400);-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component input.fi-input:where(.dark,.dark *):disabled::placeholder{-webkit-text-fill-color:var(--color-gray-500)}@supports (-webkit-touch-callout:none){.custom-fields-component input.fi-input{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.custom-fields-component input.fi-input.fi-input-has-inline-prefix{padding-inline-start:calc(var(--spacing)*0)}.custom-fields-component input.fi-input.fi-input-has-inline-suffix{padding-inline-end:calc(var(--spacing)*0)}.custom-fields-component input.fi-input.fi-align-center{text-align:center}.custom-fields-component input.fi-input.fi-align-end{text-align:end}.custom-fields-component input.fi-input.fi-align-left{text-align:left}.custom-fields-component input.fi-input.fi-align-right{text-align:end}.custom-fields-component input.fi-input.fi-align-between,.custom-fields-component input.fi-input.fi-align-justify{text-align:justify}.custom-fields-component input[type=date].fi-input,.custom-fields-component input[type=datetime-local].fi-input,.custom-fields-component input[type=time].fi-input{background-color:#ffffff03}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=date].fi-input,.custom-fields-component input[type=datetime-local].fi-input,.custom-fields-component input[type=time].fi-input{background-color:color-mix(in oklab,var(--color-white)1%,transparent)}}.custom-fields-component input[type=range].fi-input{appearance:auto;margin-inline:auto;width:calc(100% - 1.5rem)}.custom-fields-component input[type=text].fi-one-time-code-input{inset-block:calc(var(--spacing)*0);left:calc(var(--spacing)*0);right:calc(var(--spacing)*-8);--tw-border-style:none;font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;padding-inline:calc(var(--spacing)*3);--tw-tracking:1.72rem;color:var(--gray-950);letter-spacing:1.72rem;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;background-color:#0000;border-style:none;display:block;position:absolute;transition-duration:75ms}.custom-fields-component input[type=text].fi-one-time-code-input::placeholder{color:var(--gray-400)}.custom-fields-component input[type=text].fi-one-time-code-input:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}.custom-fields-component input[type=text].fi-one-time-code-input:disabled{color:var(--gray-500);-webkit-text-fill-color:var(--color-gray-500)}.custom-fields-component input[type=text].fi-one-time-code-input:disabled::placeholder{-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component input[type=text].fi-one-time-code-input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component input[type=text].fi-one-time-code-input:where(.dark,.dark *)::placeholder{color:var(--gray-500)}.custom-fields-component input[type=text].fi-one-time-code-input:where(.dark,.dark *):disabled{color:var(--gray-400);-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component input[type=text].fi-one-time-code-input:where(.dark,.dark *):disabled::placeholder{-webkit-text-fill-color:var(--color-gray-500)}.custom-fields-component input[type=text].fi-one-time-code-input.fi-valid{caret-color:#0000}.custom-fields-component .fi-one-time-code-input-ctn{height:calc(var(--spacing)*12);position:relative}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit-field{border-color:var(--gray-950);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;display:inline-block;height:100%;width:calc(var(--spacing)*8)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit-field{border-color:color-mix(in oklab,var(--gray-950)10%,transparent)}}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit-field{background-color:var(--color-white)}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit-field:where(.dark,.dark *){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit-field:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit-field:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit-field:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit-field.fi-active{border-color:var(--primary-600);border-style:var(--tw-border-style);border-width:2px}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit-field.fi-active:where(.dark,.dark *){border-color:var(--primary-500)}.custom-fields-component input[type=radio].fi-radio-input{appearance:none;height:calc(var(--spacing)*4);width:calc(var(--spacing)*4);--tw-border-style:none;background-color:var(--color-white);color:var(--primary-600);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);border-radius:3.40282e+38px;border-style:none}.custom-fields-component input[type=radio].fi-radio-input,.custom-fields-component input[type=radio].fi-radio-input:checked{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component input[type=radio].fi-radio-input:checked{background-color:var(--primary-600);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor)}.custom-fields-component input[type=radio].fi-radio-input:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600);--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.custom-fields-component input[type=radio].fi-radio-input:checked:focus{--tw-ring-color:var(--primary-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input:checked:focus{--tw-ring-color:color-mix(in oklab,var(--primary-500)50%,transparent)}}.custom-fields-component input[type=radio].fi-radio-input:disabled{background-color:var(--gray-50);color:var(--gray-50)}.custom-fields-component input[type=radio].fi-radio-input:disabled:checked{background-color:var(--gray-400);color:var(--gray-400)}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *){color:var(--primary-500);--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):checked{background-color:var(--primary-500)}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):focus{--tw-ring-color:var(--primary-500)}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):checked:focus{--tw-ring-color:var(--primary-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):checked:focus{--tw-ring-color:color-mix(in oklab,var(--primary-400)50%,transparent)}}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):disabled{--tw-ring-color:#ffffff1a;background-color:#0000}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):disabled{--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):disabled:checked{background-color:var(--gray-600)}.custom-fields-component input[type=radio].fi-radio-input:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid{color:var(--danger-600);--tw-ring-color:var(--danger-600)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:checked{background-color:var(--danger-600)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:focus{--tw-ring-color:var(--danger-600)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:checked:focus{--tw-ring-color:var(--danger-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:checked:focus{--tw-ring-color:color-mix(in oklab,var(--danger-500)50%,transparent)}}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:where(.dark,.dark *){color:var(--danger-500);--tw-ring-color:var(--danger-500)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:where(.dark,.dark *):checked{background-color:var(--danger-500)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:where(.dark,.dark *):focus{--tw-ring-color:var(--danger-500)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:where(.dark,.dark *):checked:focus{--tw-ring-color:var(--danger-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:where(.dark,.dark *):checked:focus{--tw-ring-color:color-mix(in oklab,var(--danger-400)50%,transparent)}}.custom-fields-component select.fi-select-input{appearance:none;--tw-border-style:none;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*1.5);width:100%;--tw-leading:calc(var(--spacing)*6);color:var(--gray-950);line-height:calc(var(--spacing)*6);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;background-color:#0000;border-style:none;display:block;padding-inline-end:calc(var(--spacing)*8);padding-inline-start:calc(var(--spacing)*3);transition-duration:75ms}.custom-fields-component select.fi-select-input:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}.custom-fields-component select.fi-select-input:disabled{color:var(--gray-500);-webkit-text-fill-color:var(--color-gray-500)}.custom-fields-component select.fi-select-input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component select.fi-select-input:where(.dark,.dark *):disabled{color:var(--gray-400);-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component select.fi-select-input optgroup{background-color:var(--color-white)}.custom-fields-component select.fi-select-input optgroup:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component select.fi-select-input option{background-color:var(--color-white)}.custom-fields-component select.fi-select-input option:where(.dark,.dark *){background-color:var(--gray-900)}@supports (-webkit-touch-callout:none){.custom-fields-component select.fi-select-input{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.custom-fields-component select.fi-select-input{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em}.custom-fields-component select.fi-select-input:where(:dir(rtl),[dir=rtl],[dir=rtl] *){background-position:.5rem}.custom-fields-component select.fi-select-input.fi-select-input-has-inline-prefix{padding-inline-start:calc(var(--spacing)*0)}.custom-fields-component .fi-select-input .fi-select-input-ctn{position:relative}.custom-fields-component .fi-select-input div[x-ref=select]{min-height:calc(var(--spacing)*9)}.custom-fields-component .fi-select-input .fi-select-input-btn{border-radius:var(--radius-lg);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));min-height:calc(var(--spacing)*9);padding-block:calc(var(--spacing)*1.5);text-align:start;width:100%;--tw-leading:calc(var(--spacing)*6);color:var(--gray-950);display:flex;line-height:calc(var(--spacing)*6);padding-inline-end:calc(var(--spacing)*8);padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-select-input .fi-select-input-btn:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-select-input .fi-select-input-btn:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-select-input .fi-select-input-btn{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em}.custom-fields-component .fi-select-input .fi-select-input-btn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){background-position:.5rem}.custom-fields-component .fi-select-input .fi-select-input-value-ctn{align-items:center;display:flex;text-wrap:wrap;width:100%;word-break:break-word}.custom-fields-component .fi-select-input .fi-select-input-value-badges-ctn{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*1.5)}.custom-fields-component .fi-select-input .fi-select-input-value-label{flex:1}.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn{--tw-translate-y:-50%;color:var(--gray-500);inset-inline-end:calc(var(--spacing)*8);position:absolute;top:50%;translate:var(--tw-translate-x)var(--tw-translate-y)}@media (hover:hover){.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn:hover{color:var(--gray-600)}}.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn:focus-visible{color:var(--gray-600);--tw-outline-style:none;outline-style:none}@media (hover:hover){.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn:where(.dark,.dark *):hover{color:var(--gray-300)}}.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn:where(.dark,.dark *):focus-visible{color:var(--gray-300)}.custom-fields-component .fi-select-input .fi-select-input-ctn-clearable .fi-select-input-btn{padding-inline-end:calc(var(--spacing)*14)}.custom-fields-component .fi-select-input .fi-dropdown-panel{max-height:calc(var(--spacing)*60);max-width:100%!important}.custom-fields-component :where(.fi-select-input .fi-select-input-options-ctn>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-100);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-select-input .fi-select-input-options-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-select-input .fi-select-input-options-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component :where(.fi-select-input .fi-select-input-option-group>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-100);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-select-input .fi-select-input-option-group:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-select-input .fi-select-input-option-group:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-select-input .fi-select-input-option-group .fi-dropdown-header{--tw-font-weight:var(--font-weight-medium);color:var(--gray-500);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-select-input .fi-select-input-option-group .fi-dropdown-header:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-select-input .fi-select-input-search-ctn{background-color:var(--color-white);position:sticky;top:calc(var(--spacing)*0);z-index:10}.custom-fields-component .fi-select-input .fi-select-input-search-ctn:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-select-input .fi-select-input-option{min-width:1px;text-wrap:wrap;word-break:break-word}.custom-fields-component .fi-select-input .fi-select-input-placeholder{color:var(--gray-400)}.custom-fields-component .fi-select-input .fi-select-input-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-select-input .fi-disabled{cursor:not-allowed;opacity:.7}.custom-fields-component .fi-select-input .fi-disabled .fi-select-input-placeholder{color:var(--gray-400)}.custom-fields-component .fi-select-input .fi-disabled .fi-select-input-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-select-input .fi-select-input-message{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-select-input .fi-select-input-message:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-select-input .fi-select-input-ctn.fi-select-input-ctn-option-labels-not-wrapped .fi-select-input-value-ctn>span{overflow:hidden;overflow-wrap:normal;text-overflow:ellipsis;text-wrap:nowrap;white-space:nowrap;word-break:normal}.custom-fields-component .fi-select-input .fi-select-input-ctn.fi-select-input-ctn-option-labels-not-wrapped .fi-select-input-option>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-fields-component .fi-input-wrp{background-color:var(--color-white);border-radius:var(--radius-lg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;display:flex;transition-duration:75ms}.custom-fields-component .fi-input-wrp:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-input-wrp:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)):focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)):where(.dark,.dark *):focus-within{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)).fi-invalid:focus-within{--tw-ring-color:var(--danger-600)}.custom-fields-component .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)).fi-invalid:where(.dark,.dark *):focus-within{--tw-ring-color:var(--danger-500)}.custom-fields-component .fi-input-wrp.fi-disabled{background-color:var(--gray-50)}.custom-fields-component .fi-input-wrp.fi-disabled:where(.dark,.dark *){background-color:#0000}.custom-fields-component .fi-input-wrp.fi-disabled:not(.fi-invalid):where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp.fi-disabled:not(.fi-invalid):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-input-wrp.fi-invalid{--tw-ring-color:var(--danger-600)}.custom-fields-component .fi-input-wrp.fi-invalid:where(.dark,.dark *){--tw-ring-color:var(--danger-500)}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix{align-items:center;column-gap:calc(var(--spacing)*3);display:none;padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix.fi-input-wrp-prefix-has-content{display:flex}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix.fi-inline{padding-inline-end:calc(var(--spacing)*2)}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix.fi-inline.fi-input-wrp-prefix-has-label{padding-inline-end:calc(var(--spacing)*1)}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix:not(.fi-inline){border-color:var(--gray-200);border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px;padding-inline-end:calc(var(--spacing)*3);padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix:not(.fi-inline):where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix:not(.fi-inline):where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-input-wrp .fi-input-wrp-content-ctn,.custom-fields-component .fi-input-wrp:not(:has(.fi-input-wrp-content-ctn))>*{flex:1;min-width:calc(var(--spacing)*0)}.custom-fields-component :is(.fi-input-wrp .fi-input-wrp-content-ctn,.fi-input-wrp:not(:has(.fi-input-wrp-content-ctn))>*).fi-input-wrp-content-ctn-ps{padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;padding-inline-end:calc(var(--spacing)*3)}.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix.fi-inline{padding-inline-start:calc(var(--spacing)*2)}.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix.fi-inline.fi-input-wrp-suffix-has-label{padding-inline-start:calc(var(--spacing)*1)}.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix:not(.fi-inline){border-color:var(--gray-200);border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px;padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix:not(.fi-inline):where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix:not(.fi-inline):where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-input-wrp .fi-input-wrp-actions{align-items:center;display:flex;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-input-wrp .fi-input-wrp-label{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));white-space:nowrap}.custom-fields-component .fi-input-wrp .fi-input-wrp-label:where(.dark,.dark *),.custom-fields-component :is(.fi-input-wrp .fi-input-wrp-prefix,.fi-input-wrp .fi-input-wrp-suffix)>.fi-icon{color:var(--gray-400)}.custom-fields-component :is(.fi-input-wrp .fi-input-wrp-prefix,.fi-input-wrp .fi-input-wrp-suffix)>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component :is(.fi-input-wrp .fi-input-wrp-prefix,.fi-input-wrp .fi-input-wrp-suffix)>.fi-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-link{align-items:center;gap:calc(var(--spacing)*1.5);justify-content:center;--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);font-weight:var(--font-weight-medium);--tw-outline-style:none;display:inline-flex;outline-style:none;position:relative}.custom-fields-component .fi-link:where(.dark,.dark *){color:var(--gray-200)}@media (hover:hover){.custom-fields-component :is(.fi-link.fi-force-enabled,.fi-link:not(.fi-disabled):not([disabled])):hover{text-decoration-line:underline}}.custom-fields-component :is(.fi-link.fi-force-enabled,.fi-link:not(.fi-disabled):not([disabled])):focus-visible{text-decoration-line:underline}.custom-fields-component .fi-link.fi-disabled:not(.fi-force-enabled),.custom-fields-component .fi-link[disabled]:not(.fi-force-enabled){cursor:default;opacity:.7}.custom-fields-component :is(.fi-link.fi-disabled:not(.fi-force-enabled),.fi-link[disabled]:not(.fi-force-enabled)):not([x-tooltip]){pointer-events:none}.custom-fields-component .fi-link.fi-size-xs{font-size:var(--text-xs);gap:calc(var(--spacing)*1);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-link.fi-size-sm{font-size:var(--text-sm);gap:calc(var(--spacing)*1);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-link.fi-size-lg,.custom-fields-component .fi-link.fi-size-md,.custom-fields-component .fi-link.fi-size-xl{font-size:var(--text-sm);gap:calc(var(--spacing)*1.5);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-link.fi-font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.custom-fields-component .fi-link.fi-font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.custom-fields-component .fi-link.fi-font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.custom-fields-component .fi-link.fi-font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.custom-fields-component .fi-link.fi-font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-link.fi-font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.custom-fields-component .fi-link.fi-font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.custom-fields-component .fi-link.fi-font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.custom-fields-component .fi-link.fi-color{color:var(--text)}.custom-fields-component .fi-link.fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-link:not(.fi-color)>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-link:not(.fi-color)>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-link .fi-link-badge-ctn{inset-inline-start:100%;top:calc(var(--spacing)*0);z-index:1;--tw-translate-x:-25%;--tw-translate-y:-75%;background-color:var(--color-white);border-radius:var(--radius-md);translate:var(--tw-translate-x)var(--tw-translate-y);width:max-content;--tw-font-weight:var(--font-weight-normal);display:flex;font-weight:var(--font-weight-normal);position:absolute}@media (hover:hover){.custom-fields-component .fi-link .fi-link-badge-ctn:hover{text-decoration-line:none}}.custom-fields-component .fi-link .fi-link-badge-ctn:focus-visible{text-decoration-line:none}.custom-fields-component .fi-link .fi-link-badge-ctn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:25%;translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-link .fi-link-badge-ctn:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component p>.fi-link,.custom-fields-component span>.fi-link{padding-bottom:2px;text-align:inherit;vertical-align:middle}.custom-fields-component .fi-loading-indicator{animation:var(--animate-spin)}.custom-fields-component .fi-loading-section{animation:var(--animate-pulse)}.custom-fields-component :is(.fi-modal.fi-modal-slide-over,.fi-modal.fi-width-screen)>.fi-modal-window-ctn>.fi-modal-window{height:100dvh}.custom-fields-component :is(.fi-modal.fi-modal-slide-over,.fi-modal.fi-width-screen)>.fi-modal-window-ctn>.fi-modal-window .fi-modal-content{flex:1}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window{margin-inline-start:auto;overflow-y:auto}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-start,.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-end{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component :is(.fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-start,.fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-end):where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-end,.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-start{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window .fi-modal-header{column-gap:calc(var(--spacing)*3)}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg{margin-block:calc(var(--spacing)*-2);margin-inline-start:calc(var(--spacing)*-2);padding:calc(var(--spacing)*2)}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window .fi-modal-close-btn{inset-inline-end:calc(var(--spacing)*6);top:calc(var(--spacing)*6)}.custom-fields-component .fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen):not(.fi-modal-has-sticky-header):not(.fi-modal-has-sticky-footer)>.fi-modal-window-ctn{overflow-y:auto}.custom-fields-component :is(.fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen).fi-modal-has-sticky-header,.fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen).fi-modal-has-sticky-footer)>.fi-modal-window-ctn>.fi-modal-window{max-height:calc(100dvh - 2rem);overflow-y:auto}.custom-fields-component .fi-modal:not(.fi-modal-slide-over)>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-start,.custom-fields-component .fi-modal:not(.fi-modal-slide-over)>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-end{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;opacity:0;scale:var(--tw-scale-x)var(--tw-scale-y)}.custom-fields-component .fi-modal:not(.fi-modal-slide-over)>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-end,.custom-fields-component .fi-modal:not(.fi-modal-slide-over)>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-start{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;opacity:1;scale:var(--tw-scale-x)var(--tw-scale-y)}.custom-fields-component .fi-modal:not(.fi-modal-slide-over)>.fi-modal-window-ctn>.fi-modal-window .fi-modal-close-btn{inset-inline-end:calc(var(--spacing)*4);top:calc(var(--spacing)*4)}.custom-fields-component .fi-modal.fi-align-start:not(.fi-modal-has-sticky-header)>.fi-modal-window-ctn>.fi-modal-window-has-icon .fi-modal-content,.custom-fields-component .fi-modal.fi-align-start:not(.fi-modal-has-sticky-header)>.fi-modal-window-ctn>.fi-modal-window-has-icon .fi-modal-footer:not(.fi-align-center){padding-inline-end:calc(var(--spacing)*6);padding-inline-start:5.25rem}.custom-fields-component .fi-modal:not(.fi-align-start)>.fi-modal-window-ctn>.fi-modal-window .fi-modal-content,.custom-fields-component .fi-modal:not(.fi-align-start)>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer{padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-modal>.fi-modal-close-overlay{background-color:var(--gray-950);inset:calc(var(--spacing)*0);position:fixed;z-index:40}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-close-overlay{background-color:color-mix(in oklab,var(--gray-950)50%,transparent)}}.custom-fields-component .fi-modal>.fi-modal-close-overlay:where(.dark,.dark *){background-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-close-overlay:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-950)75%,transparent)}}.custom-fields-component .fi-modal>.fi-modal-window-ctn{display:grid;grid-template-rows:1fr auto 1fr;inset:calc(var(--spacing)*0);justify-items:center;min-height:100%;position:fixed;z-index:40}@media (min-width:40rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn{grid-template-rows:1fr auto 3fr}}.custom-fields-component .fi-modal>.fi-modal-window-ctn.fi-clickable{cursor:pointer}.custom-fields-component .fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen)>.fi-modal-window-ctn{padding:calc(var(--spacing)*4)}.custom-fields-component .fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen)>.fi-modal-window-ctn .fi-modal-window{border-radius:var(--radius-xl);margin-inline:auto}.custom-fields-component .fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen).fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.custom-fields-component .fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen).fi-modal-has-sticky-footer>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer{border-bottom-left-radius:var(--radius-xl);border-bottom-right-radius:var(--radius-xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window{background-color:var(--color-white);cursor:default;pointer-events:auto;--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);width:100%;--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent);display:flex;flex-direction:column;grid-row-start:2;position:relative}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-header{display:flex;padding-inline:calc(var(--spacing)*6);padding-top:calc(var(--spacing)*6)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-header.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-description{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));margin-top:calc(var(--spacing)*2)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-content{display:flex;flex-direction:column;padding-block:calc(var(--spacing)*6);row-gap:calc(var(--spacing)*4)}.custom-fields-component :is(.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-start,.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-left) .fi-modal-header{column-gap:calc(var(--spacing)*5)}.custom-fields-component :is(.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-start,.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-left) .fi-modal-icon-bg{padding:calc(var(--spacing)*2)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-center .fi-modal-header{flex-direction:column;text-align:center}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-center .fi-modal-icon-ctn{align-items:center;display:flex;justify-content:center;margin-bottom:calc(var(--spacing)*5)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-center .fi-modal-icon-bg{padding:calc(var(--spacing)*3)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-hidden{display:none}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-xs{max-width:var(--container-xs)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-sm{max-width:var(--container-sm)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-md{max-width:var(--container-md)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-lg{max-width:var(--container-lg)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-xl{max-width:var(--container-xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-2xl{max-width:var(--container-2xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-3xl{max-width:var(--container-3xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-4xl{max-width:var(--container-4xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-5xl{max-width:var(--container-5xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-6xl{max-width:var(--container-6xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-7xl{max-width:var(--container-7xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-full{max-width:100%}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-min{max-width:min-content}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-max{max-width:max-content}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-fit{max-width:fit-content}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-prose{max-width:65ch}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen-sm{max-width:var(--breakpoint-sm)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen-md{max-width:var(--breakpoint-md)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen-lg{max-width:var(--breakpoint-lg)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen-xl{max-width:var(--breakpoint-xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen-2xl{max-width:var(--breakpoint-2xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen{inset:calc(var(--spacing)*0);position:fixed}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter,.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave{--tw-duration:.3s;transition-duration:.3s}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-close-btn.fi-align-center:not(.fi-modal-window-has-icon) .fi-modal-heading{margin-inline-start:calc(var(--spacing)*6)}.custom-fields-component :is(.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-close-btn:not(.fi-modal-window-has-icon),.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-close-btn.fi-align-start,.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-close-btn.fi-align-left) .fi-modal-heading{margin-inline-end:calc(var(--spacing)*6)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-close-btn{position:absolute}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer{width:100%}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer .fi-modal-footer-actions{gap:calc(var(--spacing)*3)}.custom-fields-component :is(.fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-start,.fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-left) .fi-modal-footer-actions{align-items:center;display:flex;flex-wrap:wrap}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-center{padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-center .fi-modal-footer-actions{display:flex;flex-direction:column-reverse}.custom-fields-component :is(.fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-end,.fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-right) .fi-modal-footer-actions{align-items:center;display:flex;flex-flow:row-reverse wrap}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg{background-color:var(--gray-100);border-radius:3.40282e+38px}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg:where(.dark,.dark *){background-color:var(--gray-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-500)20%,transparent)}}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg>.fi-icon{color:var(--gray-500)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg>.fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg.fi-color{background-color:var(--color-100)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg.fi-color:where(.dark,.dark *){background-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg.fi-color:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-500)20%,transparent)}}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg.fi-color>.fi-icon{color:var(--color-600)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg.fi-color>.fi-icon:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header{background-color:var(--color-white);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);padding-bottom:calc(var(--spacing)*6);position:sticky;top:calc(var(--spacing)*0);z-index:10}.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window .fi-modal-content,.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer{padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-modal.fi-modal-has-sticky-footer>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer{background-color:var(--color-white);border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:1px;bottom:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*5);position:sticky}.custom-fields-component .fi-modal.fi-modal-has-sticky-footer>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal.fi-modal-has-sticky-footer>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-modal.fi-modal-has-sticky-footer>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-modal:not(.fi-modal-has-sticky-footer)>.fi-modal-window-ctn>.fi-modal-window:not(.fi-modal-window-has-content) .fi-modal-footer{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-modal:not(.fi-modal-has-sticky-footer)>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer,.custom-fields-component .fi-modal:not(.fi-modal-has-sticky-footer)>.fi-modal-window-ctn>.fi-modal-window:not(.fi-modal-window-has-content):not(.fi-modal-window-has-footer) .fi-modal-header{padding-bottom:calc(var(--spacing)*6)}.custom-fields-component .fi-modal:not(.fi-modal-has-sticky-header)>.fi-modal-window-ctn>.fi-modal-window:not(.fi-modal-window-has-icon) .fi-modal-content,.custom-fields-component .fi-modal:not(.fi-modal-has-sticky-header)>.fi-modal-window-ctn>.fi-modal-window:not(.fi-modal-window-has-icon) .fi-modal-footer{padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer{margin-top:auto}@supports (container-type:inline-size){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-center{container-type:inline-size}@container (min-width:24rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-center .fi-modal-footer-actions{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr))}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-center .fi-modal-footer-actions{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr))}}}.custom-fields-component :scope .fi-modal-trigger{display:flex}.custom-fields-component .fi-pagination{align-items:center;column-gap:calc(var(--spacing)*3);display:grid;grid-template-columns:1fr auto 1fr}.custom-fields-component .fi-pagination:empty{display:none}.custom-fields-component .fi-pagination .fi-pagination-previous-btn{justify-self:flex-start}.custom-fields-component .fi-pagination .fi-pagination-overview{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);display:none;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-pagination .fi-pagination-overview:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select-ctn{grid-column-start:2;justify-self:center}.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select:not(.fi-compact){display:none}.custom-fields-component .fi-pagination .fi-pagination-next-btn{grid-column-start:3;justify-self:flex-end}.custom-fields-component .fi-pagination .fi-pagination-items{background-color:var(--color-white);border-radius:var(--radius-lg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);display:none;justify-self:flex-end}.custom-fields-component .fi-pagination .fi-pagination-items:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-items:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-pagination .fi-pagination-items:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-items:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-pagination .fi-pagination-item{border-color:var(--gray-200);border-inline-style:var(--tw-border-style);border-inline-width:.5px}.custom-fields-component .fi-pagination .fi-pagination-item:first-child{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.custom-fields-component .fi-pagination .fi-pagination-item:last-child{border-inline-end-style:var(--tw-border-style);border-inline-end-width:0}.custom-fields-component .fi-pagination .fi-pagination-item:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-item:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-pagination .fi-pagination-item.fi-active .fi-pagination-item-btn{background-color:var(--gray-50)}.custom-fields-component .fi-pagination .fi-pagination-item.fi-active .fi-pagination-item-btn:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-item.fi-active .fi-pagination-item-btn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-pagination .fi-pagination-item.fi-active .fi-pagination-item-label{color:var(--primary-700)}.custom-fields-component .fi-pagination .fi-pagination-item.fi-active .fi-pagination-item-label:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-pagination .fi-pagination-item:first-of-type .fi-pagination-item-btn{border-end-start-radius:var(--radius-lg);border-start-start-radius:var(--radius-lg)}.custom-fields-component .fi-pagination .fi-pagination-item:last-of-type .fi-pagination-item-btn{border-end-end-radius:var(--radius-lg);border-start-end-radius:var(--radius-lg)}.custom-fields-component .fi-pagination .fi-pagination-item.fi-disabled .fi-pagination-item-label{color:var(--gray-500)}.custom-fields-component .fi-pagination .fi-pagination-item.fi-disabled .fi-pagination-item-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-pagination .fi-pagination-item-btn{padding:calc(var(--spacing)*2);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;--tw-outline-style:none;display:flex;outline-style:none;overflow:hidden;position:relative;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-pagination .fi-pagination-item-btn:enabled:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-pagination .fi-pagination-item-btn:enabled:focus-visible{z-index:10;--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}@media (hover:hover){.custom-fields-component .fi-pagination .fi-pagination-item-btn:enabled:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-item-btn:enabled:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-pagination .fi-pagination-item-btn:enabled:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-pagination .fi-pagination-item-btn:hover .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-pagination .fi-pagination-item-btn:hover .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-pagination .fi-pagination-item-btn .fi-icon{color:var(--gray-400);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-pagination .fi-pagination-item-btn .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-pagination .fi-pagination-item-btn .fi-pagination-item-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-inline:calc(var(--spacing)*1.5);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-700);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-pagination .fi-pagination-item-btn .fi-pagination-item-label:where(.dark,.dark *){color:var(--gray-200)}@supports (container-type:inline-size){.custom-fields-component .fi-pagination{container-type:inline-size}@container (min-width:28rem){.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select:not(.fi-compact){display:inline}.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select.fi-compact{display:none}}@container (min-width:56rem){.custom-fields-component .fi-pagination:not(.fi-simple) .fi-pagination-next-btn,.custom-fields-component .fi-pagination:not(.fi-simple) .fi-pagination-previous-btn{display:none}.custom-fields-component .fi-pagination .fi-pagination-overview{display:inline}.custom-fields-component .fi-pagination .fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select:not(.fi-compact){display:inline}.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select.fi-compact{display:none}}@media (min-width:48rem){.custom-fields-component .fi-pagination:not(.fi-simple) .fi-pagination-next-btn,.custom-fields-component .fi-pagination:not(.fi-simple) .fi-pagination-previous-btn{display:none}.custom-fields-component .fi-pagination .fi-pagination-overview{display:inline}.custom-fields-component .fi-pagination .fi-pagination-items{display:flex}}}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-divided>.fi-section-content-ctn>.fi-section-content>*,.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-divided)>.fi-section-content-ctn>.fi-section-content{padding:calc(var(--spacing)*6)}.custom-fields-component .fi-section:not(.fi-section-not-contained)>.fi-section-content-ctn>.fi-section-footer{border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:1px;padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-section:not(.fi-section-not-contained)>.fi-section-content-ctn>.fi-section-footer:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained)>.fi-section-content-ctn>.fi-section-footer:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside){background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside):where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-compact{border-radius:var(--radius-lg)}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-secondary{background-color:var(--gray-50)}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-secondary:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-secondary:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside)>.fi-section-header{padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-section-has-header:not(.fi-collapsed)>.fi-section-content-ctn{border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:1px}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-section-has-header:not(.fi-collapsed)>.fi-section-content-ctn:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-section-has-header:not(.fi-collapsed)>.fi-section-content-ctn:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside>.fi-section-content-ctn{background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}@media (min-width:48rem){.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside>.fi-section-content-ctn{grid-column:span 2/span 2}}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside>.fi-section-content-ctn:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside>.fi-section-content-ctn:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside.fi-compact>.fi-section-content-ctn{border-radius:var(--radius-lg)}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside.fi-secondary>.fi-section-content-ctn{background-color:var(--gray-50)}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside.fi-secondary>.fi-section-content-ctn:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside.fi-secondary>.fi-section-content-ctn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-compact:not(.fi-aside)>.fi-section-header{padding-block:calc(var(--spacing)*2.5);padding-inline:calc(var(--spacing)*4)}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-compact.fi-divided>.fi-section-content-ctn>.fi-section-content>*,.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-compact:not(.fi-divided)>.fi-section-content-ctn>.fi-section-content{padding:calc(var(--spacing)*4)}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-compact>.fi-section-footer{padding-block:calc(var(--spacing)*2.5);padding-inline:calc(var(--spacing)*4)}@media (min-width:48rem){.custom-fields-component .fi-section.fi-section-not-contained.fi-aside>.fi-section-content-ctn{grid-column:span 2/span 2}}.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside),.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside)>.fi-section-content-ctn{display:grid;row-gap:calc(var(--spacing)*4)}.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside).fi-divided>.fi-section-content-ctn>.fi-section-content>*{padding-block:calc(var(--spacing)*6)}.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside).fi-compact,.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside).fi-compact>.fi-section-content-ctn{row-gap:calc(var(--spacing)*2.5)}.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside).fi-compact.fi-divided>.fi-section-content-ctn>.fi-section-content>*{padding-block:calc(var(--spacing)*4)}.custom-fields-component :where(.fi-section.fi-divided>.fi-section-content-ctn>.fi-section-content>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-section.fi-divided>.fi-section-content-ctn>.fi-section-content:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-section.fi-divided>.fi-section-content-ctn>.fi-section-content:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-section.fi-aside{align-items:flex-start;column-gap:calc(var(--spacing)*6);display:grid;grid-template-columns:repeat(1,minmax(0,1fr));row-gap:calc(var(--spacing)*4)}@media (min-width:48rem){.custom-fields-component .fi-section.fi-aside{grid-template-columns:repeat(3,minmax(0,1fr))}}.custom-fields-component .fi-section.fi-collapsible>.fi-section-header{cursor:pointer}.custom-fields-component .fi-section.fi-collapsed>.fi-section-header>.fi-section-collapse-btn{rotate:180deg}.custom-fields-component .fi-section.fi-collapsed>.fi-section-content-ctn{height:calc(var(--spacing)*0);visibility:hidden;--tw-border-style:none;border-style:none;overflow:hidden;position:absolute}@media (min-width:48rem){.custom-fields-component .fi-section.fi-section-has-content-before>.fi-section-content-ctn{order:-9999}}.custom-fields-component .fi-section>.fi-section-header{align-items:center;display:flex;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-section>.fi-section-header>.fi-icon{color:var(--gray-400);flex-shrink:0}.custom-fields-component .fi-section>.fi-section-header>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-section>.fi-section-header>.fi-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-section>.fi-section-header>.fi-icon.fi-color:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-section>.fi-section-header>.fi-icon.fi-size-sm{margin-top:calc(var(--spacing)*1)}.custom-fields-component .fi-section>.fi-section-header>.fi-icon.fi-size-md{margin-top:calc(var(--spacing)*.5)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn .fi-link,.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn .fi-sc-text{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn .fi-btn.fi-size-xs{margin-block:calc(var(--spacing)*-.5)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn .fi-btn.fi-size-sm{margin-block:calc(var(--spacing)*-1)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn .fi-btn.fi-size-md{margin-block:calc(var(--spacing)*-1.5)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn .fi-btn.fi-size-lg{margin-block:calc(var(--spacing)*-2)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn .fi-btn.fi-size-xl{margin-block:calc(var(--spacing)*-2.5)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-collapse-btn{flex-shrink:0;margin-block:calc(var(--spacing)*-1.5)}.custom-fields-component .fi-section .fi-section-header-text-ctn{display:grid;flex:1;row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-section .fi-section-header-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-section .fi-section-header-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-section .fi-section-header-description{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));overflow:hidden;overflow-wrap:break-word}.custom-fields-component .fi-section .fi-section-header-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-tabs{column-gap:calc(var(--spacing)*1);display:flex;max-width:100%;overflow-x:auto}.custom-fields-component .fi-tabs.fi-contained{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);padding-block:calc(var(--spacing)*2.5);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-tabs.fi-contained:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs.fi-contained:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-tabs:not(.fi-contained){background-color:var(--color-white);border-radius:var(--radius-xl);padding:calc(var(--spacing)*2);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent);margin-inline:auto}.custom-fields-component .fi-tabs:not(.fi-contained):where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs:not(.fi-contained):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-tabs.fi-vertical{column-gap:calc(var(--spacing)*0);flex-direction:column;overflow:hidden auto;row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-tabs.fi-vertical.fi-contained{border-bottom-style:var(--tw-border-style);border-bottom-width:0;border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-tabs.fi-vertical:not(.fi-contained){margin-inline:calc(var(--spacing)*0)}.custom-fields-component .fi-tabs.fi-vertical .fi-tabs-item{justify-content:flex-start}.custom-fields-component .fi-tabs-item{align-items:center;border-radius:var(--radius-lg);column-gap:calc(var(--spacing)*2);font-size:var(--text-sm);justify-content:center;line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));white-space:nowrap;--tw-duration:75ms;--tw-outline-style:none;display:flex;outline-style:none;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-tabs-item:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-tabs-item:focus-visible{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-tabs-item:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs-item:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-tabs-item:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs-item:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-tabs-item.fi-active{background-color:var(--gray-50)}.custom-fields-component .fi-tabs-item.fi-active:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs-item.fi-active:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-tabs-item.fi-active .fi-icon,.custom-fields-component .fi-tabs-item.fi-active .fi-tabs-item-label{color:var(--primary-700)}.custom-fields-component :is(.fi-tabs-item.fi-active .fi-tabs-item-label,.fi-tabs-item.fi-active .fi-icon):where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-tabs-item :not(.fi-active):hover .fi-tabs-item-label,.custom-fields-component .fi-tabs-item :not(.fi-active):hover .fi-tabs-item-label:is(:where(.group):focus-visible *){color:var(--gray-700)}.custom-fields-component .fi-tabs-item :not(.fi-active):hover .fi-tabs-item-label:where(.dark,.dark *),.custom-fields-component .fi-tabs-item :not(.fi-active):hover .fi-tabs-item-label:where(.dark,.dark *):is(:where(.group):focus-visible *){color:var(--gray-200)}.custom-fields-component .fi-tabs-item :not(.fi-active):focus-visible .fi-tabs-item-label{color:var(--gray-700)}.custom-fields-component .fi-tabs-item :not(.fi-active):focus-visible .fi-tabs-item-label:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-tabs-item .fi-tabs-item-label{color:var(--gray-500);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-tabs-item .fi-tabs-item-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-tabs-item .fi-icon{color:var(--gray-400);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;flex-shrink:0;transition-duration:75ms}.custom-fields-component .fi-tabs-item .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-tabs-item .fi-badge{width:max-content}.custom-fields-component .fi-toggle{background-color:var(--gray-200);border-style:var(--tw-border-style);cursor:pointer;height:calc(var(--spacing)*6);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:calc(var(--spacing)*11);--tw-duration:.2s;--tw-ease:var(--ease-in-out);transition-duration:.2s;transition-timing-function:var(--ease-in-out);--tw-outline-style:none;border-color:#0000;border-radius:3.40282e+38px;border-width:2px;display:inline-flex;flex-shrink:0;outline-style:none;position:relative}.custom-fields-component .fi-toggle:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.custom-fields-component .fi-toggle:disabled{opacity:.7;pointer-events:none}.custom-fields-component .fi-toggle:where(.dark,.dark *){background-color:var(--gray-700)}.custom-fields-component .fi-toggle:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500);--tw-ring-offset-color:var(--gray-900)}.custom-fields-component .fi-toggle:disabled,.custom-fields-component .fi-toggle[disabled]{opacity:.7;pointer-events:none}.custom-fields-component .fi-toggle.fi-color{background-color:var(--bg)}.custom-fields-component .fi-toggle.fi-color:where(.dark,.dark *){background-color:var(--dark-bg)}.custom-fields-component .fi-toggle.fi-color .fi-icon{color:var(--text)}.custom-fields-component .fi-toggle.fi-hidden{display:none}.custom-fields-component .fi-toggle>:first-child{background-color:var(--color-white);height:calc(var(--spacing)*5);pointer-events:none;transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);width:calc(var(--spacing)*5);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.2s;--tw-ease:var(--ease-in-out);border-radius:3.40282e+38px;display:inline-block;position:relative;transition-duration:.2s;transition-timing-function:var(--ease-in-out)}.custom-fields-component .fi-toggle>:first-child>*{align-items:center;display:flex;height:100%;inset:calc(var(--spacing)*0);justify-content:center;position:absolute;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:100%}.custom-fields-component .fi-toggle .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-toggle .fi-icon:where(.dark,.dark *){color:var(--gray-700)}.custom-fields-component .fi-toggle.fi-toggle-on>:first-child{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-toggle.fi-toggle-on>:first-child:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(var(--spacing)*-5);translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-toggle.fi-toggle-on>:first-child>:first-child{opacity:0;--tw-duration:.1s;--tw-ease:var(--ease-out);transition-duration:.1s;transition-timing-function:var(--ease-out)}.custom-fields-component .fi-toggle.fi-toggle-on>:first-child>:last-child{opacity:1;--tw-duration:.2s;--tw-ease:var(--ease-in);transition-duration:.2s;transition-timing-function:var(--ease-in)}.custom-fields-component .fi-toggle.fi-toggle-off>:first-child{--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-toggle.fi-toggle-off>:first-child>:first-child{opacity:1;--tw-duration:.2s;--tw-ease:var(--ease-in);transition-duration:.2s;transition-timing-function:var(--ease-in)}.custom-fields-component .fi-toggle.fi-toggle-off>:first-child>:last-child{opacity:0;--tw-duration:.1s;--tw-ease:var(--ease-out);transition-duration:.1s;transition-timing-function:var(--ease-out)}.custom-fields-component .fi-sortable-ghost{opacity:.3}.custom-fields-component .fi-ac{gap:calc(var(--spacing)*3)}.custom-fields-component .fi-ac:not(.fi-width-full){align-items:center;display:flex;flex-wrap:wrap}.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-left,.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-start{justify-content:flex-start}.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-center{justify-content:center}.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-end,.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-right{flex-direction:row-reverse}.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-between,.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-justify{justify-content:space-between}.custom-fields-component .fi-ac.fi-width-full{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr))}.custom-fields-component .CodeMirror{color:#000;direction:ltr;font-family:monospace;height:300px}.custom-fields-component .CodeMirror-lines{padding:4px 0}.custom-fields-component .CodeMirror pre.CodeMirror-line,.custom-fields-component .CodeMirror pre.CodeMirror-line-like{padding:0 4px}.custom-fields-component .CodeMirror-gutter-filler,.custom-fields-component .CodeMirror-scrollbar-filler{background-color:#fff}.custom-fields-component .CodeMirror-gutters{background-color:#f7f7f7;border-right:1px solid #ddd;white-space:nowrap}.custom-fields-component .CodeMirror-linenumber{color:#999;min-width:20px;padding:0 3px 0 5px;text-align:right;white-space:nowrap}.custom-fields-component .CodeMirror-guttermarker{color:#000}.custom-fields-component .CodeMirror-guttermarker-subtle{color:#999}.custom-fields-component .CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.custom-fields-component .CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.custom-fields-component .cm-fat-cursor .CodeMirror-cursor{background:#7e7;border:0!important;width:auto}.custom-fields-component .cm-fat-cursor div.CodeMirror-cursors{z-index:1}.custom-fields-component .cm-fat-cursor .CodeMirror-line::selection{background:0 0}.custom-fields-component .cm-fat-cursor .CodeMirror-line>span::selection{background:0 0}.custom-fields-component .cm-fat-cursor .CodeMirror-line>span>span::selection{background:0 0}.custom-fields-component .cm-fat-cursor .CodeMirror-line::-moz-selection,.custom-fields-component .cm-fat-cursor .CodeMirror-line>span::-moz-selection{background:0 0}.custom-fields-component .cm-fat-cursor .CodeMirror-line>span>span::-moz-selection{background:0 0}.custom-fields-component .cm-fat-cursor{caret-color:#0000}@keyframes blink{50%{background-color:#0000}}.custom-fields-component .cm-tab{display:inline-block;-webkit-text-decoration:inherit;text-decoration:inherit}.custom-fields-component .CodeMirror-rulers{inset:-50px 0 0;overflow:hidden;position:absolute}.custom-fields-component .CodeMirror-ruler{border-left:1px solid #ccc;bottom:0;position:absolute;top:0}.custom-fields-component .cm-s-default .cm-header{color:#00f}.custom-fields-component .cm-s-default .cm-quote{color:#090}.custom-fields-component .cm-negative{color:#d44}.custom-fields-component .cm-positive{color:#292}.custom-fields-component .cm-header,.custom-fields-component .cm-strong{font-weight:700}.custom-fields-component .cm-em{font-style:italic}.custom-fields-component .cm-link{text-decoration:underline}.custom-fields-component .cm-strikethrough{text-decoration:line-through}.custom-fields-component .cm-s-default .cm-keyword{color:#708}.custom-fields-component .cm-s-default .cm-atom{color:#219}.custom-fields-component .cm-s-default .cm-number{color:#164}.custom-fields-component .cm-s-default .cm-def{color:#00f}.custom-fields-component .cm-s-default .cm-variable-2{color:#05a}.custom-fields-component .cm-s-default .cm-type,.custom-fields-component .cm-s-default .cm-variable-3{color:#085}.custom-fields-component .cm-s-default .cm-comment{color:#a50}.custom-fields-component .cm-s-default .cm-string{color:#a11}.custom-fields-component .cm-s-default .cm-string-2{color:#f50}.custom-fields-component .cm-s-default .cm-meta,.custom-fields-component .cm-s-default .cm-qualifier{color:#555}.custom-fields-component .cm-s-default .cm-builtin{color:#30a}.custom-fields-component .cm-s-default .cm-bracket{color:#997}.custom-fields-component .cm-s-default .cm-tag{color:#170}.custom-fields-component .cm-s-default .cm-attribute{color:#00c}.custom-fields-component .cm-s-default .cm-hr{color:#999}.custom-fields-component .cm-s-default .cm-link{color:#00c}.custom-fields-component .cm-invalidchar,.custom-fields-component .cm-s-default .cm-error{color:red}.custom-fields-component .CodeMirror-composing{border-bottom:2px solid}.custom-fields-component div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}.custom-fields-component div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.custom-fields-component .CodeMirror-matchingtag{background:#ff96004d}.custom-fields-component .CodeMirror-activeline-background{background:#e8f2ff}.custom-fields-component .CodeMirror{background:#fff;overflow:hidden;position:relative}.custom-fields-component .CodeMirror-scroll{height:100%;margin-bottom:-50px;margin-right:-50px;outline:0;overflow:scroll!important;padding-bottom:50px;position:relative;z-index:0}.custom-fields-component .CodeMirror-sizer{border-right:50px solid #0000;position:relative}.custom-fields-component .CodeMirror-gutter-filler,.custom-fields-component .CodeMirror-hscrollbar,.custom-fields-component .CodeMirror-scrollbar-filler,.custom-fields-component .CodeMirror-vscrollbar{display:none;outline:0;position:absolute;z-index:6}.custom-fields-component .CodeMirror-vscrollbar{overflow:hidden scroll;right:0;top:0}.custom-fields-component .CodeMirror-hscrollbar{bottom:0;left:0;overflow:scroll hidden}.custom-fields-component .CodeMirror-scrollbar-filler{bottom:0;right:0}.custom-fields-component .CodeMirror-gutter-filler{bottom:0;left:0}.custom-fields-component .CodeMirror-gutters{left:0;min-height:100%;position:absolute;top:0;z-index:3}.custom-fields-component .CodeMirror-gutter{display:inline-block;height:100%;margin-bottom:-50px;vertical-align:top;white-space:normal}.custom-fields-component .CodeMirror-gutter-wrapper{background:0 0!important;border:none!important;position:absolute;z-index:4}.custom-fields-component .CodeMirror-gutter-background{bottom:0;position:absolute;top:0;z-index:4}.custom-fields-component .CodeMirror-gutter-elt{cursor:default;position:absolute;z-index:4}.custom-fields-component .CodeMirror-gutter-wrapper ::selection{background-color:#0000}.custom-fields-component .CodeMirror-lines{cursor:text;min-height:1px}.custom-fields-component .CodeMirror pre.CodeMirror-line,.custom-fields-component .CodeMirror pre.CodeMirror-line-like{font-family:inherit;font-size:inherit;white-space:pre;word-wrap:normal;color:inherit;line-height:inherit;z-index:2;-webkit-tap-highlight-color:transparent;background:0 0;border-radius:0;border-width:0;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual;margin:0;overflow:visible;position:relative}.custom-fields-component .CodeMirror-wrap pre.CodeMirror-line,.custom-fields-component .CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.custom-fields-component .CodeMirror-linebackground{inset:0;position:absolute;z-index:0}.custom-fields-component .CodeMirror-linewidget{padding:.1px;position:relative;z-index:2}.custom-fields-component .CodeMirror-code{outline:0}.custom-fields-component .CodeMirror-gutter,.custom-fields-component .CodeMirror-gutters,.custom-fields-component .CodeMirror-linenumber,.custom-fields-component .CodeMirror-scroll,.custom-fields-component .CodeMirror-sizer{box-sizing:content-box}.custom-fields-component .CodeMirror-measure{height:0;overflow:hidden;position:absolute;visibility:hidden;width:100%}.custom-fields-component .CodeMirror-cursor{pointer-events:none;position:absolute}.custom-fields-component .CodeMirror-measure pre{position:static}.custom-fields-component div.CodeMirror-cursors{position:relative;visibility:hidden;z-index:3}.custom-fields-component .CodeMirror-focused div.CodeMirror-cursors,.custom-fields-component div.CodeMirror-dragcursors{visibility:visible}.custom-fields-component .CodeMirror-selected{background:#d9d9d9}.custom-fields-component .CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.custom-fields-component .CodeMirror-crosshair{cursor:crosshair}.custom-fields-component .CodeMirror-line::selection{background:#d7d4f0}.custom-fields-component .CodeMirror-line>span::selection{background:#d7d4f0}.custom-fields-component .CodeMirror-line>span>span::selection{background:#d7d4f0}.custom-fields-component .CodeMirror-line::-moz-selection,.custom-fields-component .CodeMirror-line>span::-moz-selection{background:#d7d4f0}.custom-fields-component .CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.custom-fields-component .cm-searching{background-color:#ff06}.custom-fields-component .cm-force-border{padding-right:.1px}@media print{.custom-fields-component .CodeMirror div.CodeMirror-cursors{visibility:hidden}}.custom-fields-component .cm-tab-wrap-hack:after{content:""}.custom-fields-component span.CodeMirror-selectedtext{background:0 0}.custom-fields-component .EasyMDEContainer{display:block}.custom-fields-component .CodeMirror-rtl pre{direction:rtl}.custom-fields-component .EasyMDEContainer.sided--no-fullscreen{display:flex;flex-flow:wrap}.custom-fields-component .EasyMDEContainer .CodeMirror{box-sizing:border-box;font:inherit;height:auto;z-index:0;word-wrap:break-word;border:1px solid #ced4da;border-bottom-left-radius:4px;border-bottom-right-radius:4px;padding:10px}.custom-fields-component .EasyMDEContainer .CodeMirror-scroll{cursor:text}.custom-fields-component .EasyMDEContainer .CodeMirror-fullscreen{background:#fff;border-bottom-right-radius:0!important;border-right:none!important;height:auto;inset:50px 0 0;position:fixed!important;z-index:8}.custom-fields-component .EasyMDEContainer .CodeMirror-sided{width:50%!important}.custom-fields-component .EasyMDEContainer.sided--no-fullscreen .CodeMirror-sided{border-bottom-right-radius:0;border-right:none!important;flex:auto;position:relative}.custom-fields-component .EasyMDEContainer .CodeMirror-placeholder{opacity:.5}.custom-fields-component .EasyMDEContainer .CodeMirror-focused .CodeMirror-selected{background:#d9d9d9}.custom-fields-component .editor-toolbar{border-left:1px solid #ced4da;border-right:1px solid #ced4da;border-top:1px solid #ced4da;border-top-left-radius:4px;border-top-right-radius:4px;padding:9px 10px;position:relative;-webkit-user-select:none;user-select:none;-o-user-select:none}.custom-fields-component .editor-toolbar.fullscreen{background:#fff;border:0;box-sizing:border-box;height:50px;left:0;opacity:1;padding-bottom:10px;padding-top:10px;position:fixed;top:0;width:100%;z-index:9}.custom-fields-component .editor-toolbar.fullscreen:before{background:-o-linear-gradient(270deg,#fff 0,#fff0 100%);background:-ms-linear-gradient(left,#fff 0,#fff0 100%);background:linear-gradient(90deg,#fff,#fff0);height:50px;left:0;margin:0;padding:0;position:fixed;top:0;width:20px}.custom-fields-component .editor-toolbar.fullscreen:after{background:-o-linear-gradient(270deg,#fff0 0,#fff 100%);background:-ms-linear-gradient(left,#fff0 0,#fff 100%);background:linear-gradient(90deg,#fff0,#fff);height:50px;margin:0;padding:0;position:fixed;right:0;top:0;width:20px}.custom-fields-component .EasyMDEContainer.sided--no-fullscreen .editor-toolbar{width:100%}.custom-fields-component .editor-toolbar .easymde-dropdown,.custom-fields-component .editor-toolbar button{background:0 0;border:1px solid #0000;border-radius:3px;cursor:pointer;display:inline-block;height:30px;margin:0;padding:0;text-align:center;text-decoration:none!important}.custom-fields-component .editor-toolbar button{font-weight:700;min-width:30px;padding:0 6px;white-space:nowrap}.custom-fields-component .editor-toolbar button.active,.custom-fields-component .editor-toolbar button:hover{background:#fcfcfc;border-color:#95a5a6}.custom-fields-component .editor-toolbar i.separator{border-left:1px solid #d9d9d9;border-right:1px solid #fff;color:#0000;display:inline-block;margin:0 6px;text-indent:-10px;width:0}.custom-fields-component .editor-toolbar button:after{font-family:Arial,Helvetica Neue,Helvetica,sans-serif;font-size:65%;position:relative;top:2px;vertical-align:text-bottom}.custom-fields-component .editor-toolbar button.heading-1:after{content:"1"}.custom-fields-component .editor-toolbar button.heading-2:after{content:"2"}.custom-fields-component .editor-toolbar button.heading-3:after{content:"3"}.custom-fields-component .editor-toolbar button.heading-bigger:after{content:"ā–²"}.custom-fields-component .editor-toolbar button.heading-smaller:after{content:"ā–¼"}.custom-fields-component .editor-toolbar.disabled-for-preview button:not(.no-disable){opacity:.6;pointer-events:none}@media only screen and (max-width:700px){.custom-fields-component .editor-toolbar i.no-mobile{display:none}}.custom-fields-component .editor-statusbar{color:#959694;font-size:12px;padding:8px 10px;text-align:right}.custom-fields-component .EasyMDEContainer.sided--no-fullscreen .editor-statusbar{width:100%}.custom-fields-component .editor-statusbar span{display:inline-block;margin-left:1em;min-width:4em}.custom-fields-component .editor-statusbar .lines:before{content:"lines: "}.custom-fields-component .editor-statusbar .words:before{content:"words: "}.custom-fields-component .editor-statusbar .characters:before{content:"characters: "}.custom-fields-component .editor-preview-full{box-sizing:border-box;display:none;height:100%;left:0;overflow:auto;position:absolute;top:0;width:100%;z-index:7}.custom-fields-component .editor-preview-side{box-sizing:border-box;z-index:9;word-wrap:break-word;border:1px solid #ddd;bottom:0;display:none;overflow:auto;position:fixed;right:0;top:50px;width:50%}.custom-fields-component .editor-preview-active-side{display:block}.custom-fields-component .EasyMDEContainer.sided--no-fullscreen .editor-preview-active-side{flex:auto;height:auto;position:static}.custom-fields-component .editor-preview-active{display:block}.custom-fields-component .editor-preview{background:#fafafa;padding:10px}.custom-fields-component .editor-preview>p{margin-top:0}.custom-fields-component .editor-preview pre{background:#eee;margin-bottom:10px}.custom-fields-component .editor-preview table td,.custom-fields-component .editor-preview table th{border:1px solid #ddd;padding:5px}.custom-fields-component .cm-s-easymde .cm-tag{color:#63a35c}.custom-fields-component .cm-s-easymde .cm-attribute{color:#795da3}.custom-fields-component .cm-s-easymde .cm-string{color:#183691}.custom-fields-component .cm-s-easymde .cm-header-1{font-size:calc(1.375rem + 1.5vw)}.custom-fields-component .cm-s-easymde .cm-header-2{font-size:calc(1.325rem + .9vw)}.custom-fields-component .cm-s-easymde .cm-header-3{font-size:calc(1.3rem + .6vw)}.custom-fields-component .cm-s-easymde .cm-header-4{font-size:calc(1.275rem + .3vw)}.custom-fields-component .cm-s-easymde .cm-header-5{font-size:1.25rem}.custom-fields-component .cm-s-easymde .cm-header-6{font-size:1rem}.custom-fields-component .cm-s-easymde .cm-header-1,.custom-fields-component .cm-s-easymde .cm-header-2,.custom-fields-component .cm-s-easymde .cm-header-3,.custom-fields-component .cm-s-easymde .cm-header-4,.custom-fields-component .cm-s-easymde .cm-header-5,.custom-fields-component .cm-s-easymde .cm-header-6{line-height:1.2;margin-bottom:.5rem}.custom-fields-component .cm-s-easymde .cm-comment{background:#0000000d;border-radius:2px}.custom-fields-component .cm-s-easymde .cm-link{color:#7f8c8d}.custom-fields-component .cm-s-easymde .cm-url{color:#aab2b3}.custom-fields-component .cm-s-easymde .cm-quote{color:#7f8c8d;font-style:italic}.custom-fields-component .editor-toolbar .easymde-dropdown{border:1px solid #fff;border-radius:0;position:relative}.custom-fields-component .editor-toolbar .easymde-dropdown,.custom-fields-component .editor-toolbar .easymde-dropdown:hover{background:linear-gradient(to bottom right,#fff 0 84%,#333 50% 100%)}.custom-fields-component .easymde-dropdown-content{background-color:#f9f9f9;box-shadow:0 8px 16px #0003;display:block;padding:8px;position:absolute;top:30px;visibility:hidden;z-index:2}.custom-fields-component .easymde-dropdown:active .easymde-dropdown-content,.custom-fields-component .easymde-dropdown:focus .easymde-dropdown-content,.custom-fields-component .easymde-dropdown:focus-within .easymde-dropdown-content{visibility:visible}.custom-fields-component .easymde-dropdown-content button{display:block}.custom-fields-component span[data-img-src]:after{background-image:var(--bg-image);background-repeat:no-repeat;background-size:contain;content:"";display:block;height:0;max-height:100%;max-width:100%;padding-top:var(--height);width:var(--width)}.custom-fields-component .CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){background:#ff000026}.custom-fields-component .cropper-container{-webkit-touch-callout:none;direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;user-select:none}.custom-fields-component .cropper-container img{backface-visibility:hidden;display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.custom-fields-component .cropper-canvas,.custom-fields-component .cropper-crop-box,.custom-fields-component .cropper-drag-box,.custom-fields-component .cropper-modal,.custom-fields-component .cropper-wrap-box{inset:0;position:absolute}.custom-fields-component .cropper-canvas,.custom-fields-component .cropper-wrap-box{overflow:hidden}.custom-fields-component .cropper-drag-box{background-color:#fff;opacity:0}.custom-fields-component .cropper-modal{background-color:#000;opacity:.5}.custom-fields-component .cropper-view-box{display:block;height:100%;outline:1px solid #3399ffbf;overflow:hidden;width:100%}.custom-fields-component .cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.custom-fields-component .cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.3333%;left:0;top:33.3333%;width:100%}.custom-fields-component .cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.3333%;top:0;width:33.3333%}.custom-fields-component .cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.custom-fields-component .cropper-center:after,.custom-fields-component .cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.custom-fields-component .cropper-center:before{height:1px;left:-3px;top:0;width:7px}.custom-fields-component .cropper-center:after{height:7px;left:0;top:-3px;width:1px}.custom-fields-component .cropper-face,.custom-fields-component .cropper-line,.custom-fields-component .cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.custom-fields-component .cropper-face{background-color:#fff;left:0;top:0}.custom-fields-component .cropper-line{background-color:#39f}.custom-fields-component .cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.custom-fields-component .cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.custom-fields-component .cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.custom-fields-component .cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.custom-fields-component .cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.custom-fields-component .cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.custom-fields-component .cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.custom-fields-component .cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.custom-fields-component .cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.custom-fields-component .cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.custom-fields-component .cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.custom-fields-component .cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.custom-fields-component .cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.custom-fields-component .cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.custom-fields-component .cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.custom-fields-component .cropper-point.point-se{height:5px;opacity:.75;width:5px}}.custom-fields-component .cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.custom-fields-component .cropper-invisible{opacity:0}.custom-fields-component .cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.custom-fields-component .cropper-hide{display:block;height:0;position:absolute;width:0}.custom-fields-component .cropper-hidden{display:none!important}.custom-fields-component .cropper-move{cursor:move}.custom-fields-component .cropper-crop{cursor:crosshair}.custom-fields-component .cropper-disabled .cropper-drag-box,.custom-fields-component .cropper-disabled .cropper-face,.custom-fields-component .cropper-disabled .cropper-line,.custom-fields-component .cropper-disabled .cropper-point{cursor:not-allowed}.custom-fields-component .filepond--assistant{clip:rect(1px,1px,1px,1px);border:0;clip-path:inset(50%);height:1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.custom-fields-component .filepond--browser.filepond--browser{font-size:0;left:1em;margin:0;opacity:0;padding:0;position:absolute;top:1.75em;width:calc(100% - 2em)}.custom-fields-component .filepond--data{border:none;contain:strict;height:0;margin:0;padding:0;pointer-events:none;position:absolute;visibility:hidden;width:0}.custom-fields-component .filepond--drip{background:#00000003;border-radius:.5em;inset:0;opacity:.1;overflow:hidden;pointer-events:none;position:absolute}.custom-fields-component .filepond--drip-blob{background:#292625;border-radius:50%;height:8em;margin-left:-4em;margin-top:-4em;transform-origin:50%;width:8em}.custom-fields-component .filepond--drip-blob,.custom-fields-component .filepond--drop-label{left:0;position:absolute;top:0;will-change:transform,opacity}.custom-fields-component .filepond--drop-label{align-items:center;color:#4f4f4f;display:flex;height:0;justify-content:center;margin:0;right:0;-webkit-user-select:none;user-select:none}.custom-fields-component .filepond--drop-label.filepond--drop-label label{display:block;margin:0;padding:.5em}.custom-fields-component .filepond--drop-label label{cursor:default;font-size:.875em;font-weight:400;line-height:1.5;text-align:center}.custom-fields-component .filepond--label-action{-webkit-text-decoration-skip:ink;cursor:pointer;-webkit-text-decoration:underline #a7a4a4;text-decoration:underline #a7a4a4;-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto}.custom-fields-component .filepond--root[data-disabled] .filepond--drop-label label{opacity:.5}.custom-fields-component .filepond--file-action-button.filepond--file-action-button{border:none;font-family:inherit;font-size:1em;height:1.625em;line-height:inherit;margin:0;outline:none;padding:0;width:1.625em;will-change:transform,opacity}.custom-fields-component .filepond--file-action-button.filepond--file-action-button span{clip:rect(1px,1px,1px,1px);border:0;clip-path:inset(50%);height:1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.custom-fields-component .filepond--file-action-button.filepond--file-action-button svg{height:100%;width:100%}.custom-fields-component .filepond--file-action-button.filepond--file-action-button:after{content:"";inset:-.75em;position:absolute}.custom-fields-component .filepond--file-action-button{background-color:#00000080;background-image:none;border-radius:50%;box-shadow:0 0 #fff0;color:#fff;cursor:auto;transition:box-shadow .25s ease-in}.custom-fields-component .filepond--file-action-button:focus,.custom-fields-component .filepond--file-action-button:hover{box-shadow:0 0 0 .125em #ffffffe6}.custom-fields-component .filepond--file-action-button[disabled]{background-color:#00000040;color:#ffffff80}.custom-fields-component .filepond--file-action-button[hidden]{display:none}.custom-fields-component .filepond--file-info{align-items:flex-start;display:flex;flex:1;flex-direction:column;margin:0 .5em 0 0;min-width:0;pointer-events:none;position:static;-webkit-user-select:none;user-select:none;will-change:transform,opacity}.custom-fields-component .filepond--file-info *{margin:0}.custom-fields-component .filepond--file-info .filepond--file-info-main{font-size:.75em;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:100%}.custom-fields-component .filepond--file-info .filepond--file-info-sub{font-size:.625em;opacity:.5;transition:opacity .25s ease-in-out;white-space:nowrap}.custom-fields-component .filepond--file-info .filepond--file-info-sub:empty{display:none}.custom-fields-component .filepond--file-status{align-items:flex-end;display:flex;flex-direction:column;flex-grow:0;flex-shrink:0;margin:0;min-width:2.25em;pointer-events:none;position:static;text-align:right;-webkit-user-select:none;user-select:none;will-change:transform,opacity}.custom-fields-component .filepond--file-status *{margin:0;white-space:nowrap}.custom-fields-component .filepond--file-status .filepond--file-status-main{font-size:.75em;line-height:1.2}.custom-fields-component .filepond--file-status .filepond--file-status-sub{font-size:.625em;opacity:.5;transition:opacity .25s ease-in-out}.custom-fields-component .filepond--file-wrapper.filepond--file-wrapper{border:none;height:100%;margin:0;min-width:0;padding:0}.custom-fields-component .filepond--file-wrapper.filepond--file-wrapper>legend{clip:rect(1px,1px,1px,1px);border:0;clip-path:inset(50%);height:1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.custom-fields-component .filepond--file{align-items:flex-start;border-radius:.5em;color:#fff;display:flex;height:100%;padding:.5625em;position:static}.custom-fields-component .filepond--file .filepond--file-status{margin-left:auto;margin-right:2.25em}.custom-fields-component .filepond--file .filepond--processing-complete-indicator{pointer-events:none;-webkit-user-select:none;user-select:none;z-index:3}.custom-fields-component .filepond--file .filepond--file-action-button,.custom-fields-component .filepond--file .filepond--processing-complete-indicator,.custom-fields-component .filepond--file .filepond--progress-indicator{position:absolute}.custom-fields-component .filepond--file [data-align*=left]{left:.5625em}.custom-fields-component .filepond--file [data-align*=right]{right:.5625em}.custom-fields-component .filepond--file [data-align*=center]{left:calc(50% - .8125em)}.custom-fields-component .filepond--file [data-align*=bottom]{bottom:1.125em}.custom-fields-component .filepond--file [data-align=center]{top:calc(50% - .8125em)}.custom-fields-component .filepond--file .filepond--progress-indicator{margin-top:.1875em}.custom-fields-component .filepond--file .filepond--progress-indicator[data-align*=right]{margin-right:.1875em}.custom-fields-component .filepond--file .filepond--progress-indicator[data-align*=left]{margin-left:.1875em}.custom-fields-component [data-filepond-item-state*=error] .filepond--file-info,.custom-fields-component [data-filepond-item-state*=invalid] .filepond--file-info,.custom-fields-component [data-filepond-item-state=cancelled] .filepond--file-info{margin-right:2.25em}.custom-fields-component [data-filepond-item-state~=processing] .filepond--file-status-sub{opacity:0}.custom-fields-component [data-filepond-item-state~=processing] .filepond--action-abort-item-processing~.filepond--file-status .filepond--file-status-sub{opacity:.5}.custom-fields-component [data-filepond-item-state=processing-error] .filepond--file-status-sub{opacity:0}.custom-fields-component [data-filepond-item-state=processing-error] .filepond--action-retry-item-processing~.filepond--file-status .filepond--file-status-sub{opacity:.5}.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing svg{animation:fall .5s linear .125s both}.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--file-status-sub{opacity:.5}.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--file-info-sub,.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--processing-complete-indicator:not([style*=hidden])~.filepond--file-status .filepond--file-status-sub{opacity:0}.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing~.filepond--file-info .filepond--file-info-sub{opacity:.5}.custom-fields-component [data-filepond-item-state*=error] .filepond--file-wrapper,.custom-fields-component [data-filepond-item-state*=error] .filepond--panel,.custom-fields-component [data-filepond-item-state*=invalid] .filepond--file-wrapper,.custom-fields-component [data-filepond-item-state*=invalid] .filepond--panel{animation:shake .65s linear both}.custom-fields-component [data-filepond-item-state*=busy] .filepond--progress-indicator svg{animation:spin 1s linear infinite}@keyframes shake{10%,90%{transform:translate(-.0625em)}20%,80%{transform:translate(.125em)}30%,50%,70%{transform:translate(-.25em)}40%,60%{transform:translate(.25em)}}@keyframes fall{0%{animation-timing-function:ease-out;opacity:0;transform:scale(.5)}70%{animation-timing-function:ease-in-out;opacity:1;transform:scale(1.1)}to{animation-timing-function:ease-out;transform:scale(1)}}.custom-fields-component .filepond--hopper[data-hopper-state=drag-over]>*{pointer-events:none}.custom-fields-component .filepond--hopper[data-hopper-state=drag-over]:after{content:"";inset:0;position:absolute;z-index:100}.custom-fields-component .filepond--progress-indicator{z-index:103}.custom-fields-component .filepond--file-action-button{z-index:102}.custom-fields-component .filepond--file-status{z-index:101}.custom-fields-component .filepond--file-info{z-index:100}.custom-fields-component .filepond--item{left:0;margin:.25em;padding:0;position:absolute;right:0;top:0;touch-action:auto;will-change:transform,opacity;z-index:1}.custom-fields-component .filepond--item>.filepond--panel{z-index:-1}.custom-fields-component .filepond--item>.filepond--panel .filepond--panel-bottom{box-shadow:0 .0625em .125em -.0625em #00000040}.custom-fields-component .filepond--item>.filepond--file-wrapper,.custom-fields-component .filepond--item>.filepond--panel{transition:opacity .15s ease-out}.custom-fields-component .filepond--item[data-drag-state]{cursor:-webkit-grab;cursor:grab}.custom-fields-component .filepond--item[data-drag-state]>.filepond--panel{box-shadow:0 0 #0000;transition:box-shadow .125s ease-in-out}.custom-fields-component .filepond--item[data-drag-state=drag]{cursor:-webkit-grabbing;cursor:grabbing}.custom-fields-component .filepond--item[data-drag-state=drag]>.filepond--panel{box-shadow:0 .125em .3125em #00000053}.custom-fields-component .filepond--item[data-drag-state]:not([data-drag-state=idle]){z-index:2}.custom-fields-component .filepond--item-panel{background-color:#64605e}.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--item-panel{background-color:#369763}.custom-fields-component [data-filepond-item-state*=error] .filepond--item-panel,.custom-fields-component [data-filepond-item-state*=invalid] .filepond--item-panel{background-color:#c44e47}.custom-fields-component .filepond--item-panel{border-radius:.5em;transition:background-color .25s}.custom-fields-component .filepond--list-scroller{left:0;margin:0;position:absolute;right:0;top:0;will-change:transform}.custom-fields-component .filepond--list-scroller[data-state=overflow] .filepond--list{bottom:0;right:0}.custom-fields-component .filepond--list-scroller[data-state=overflow]{-webkit-overflow-scrolling:touch;-webkit-mask:linear-gradient(#000 calc(100% - .5em),#0000);mask:linear-gradient(#000 calc(100% - .5em),#0000);overflow:hidden scroll}.custom-fields-component .filepond--list-scroller::-webkit-scrollbar{background:0 0}.custom-fields-component .filepond--list-scroller::-webkit-scrollbar:vertical{width:1em}.custom-fields-component .filepond--list-scroller::-webkit-scrollbar:horizontal{height:0}.custom-fields-component .filepond--list-scroller::-webkit-scrollbar-thumb{background-clip:content-box;background-color:#0000004d;border:.3125em solid #0000;border-radius:99999px}.custom-fields-component .filepond--list.filepond--list{list-style-type:none;margin:0;padding:0;position:absolute;top:0;will-change:transform}.custom-fields-component .filepond--list{left:.75em;right:.75em}.custom-fields-component .filepond--root[data-style-panel-layout~=integrated]{height:100%;margin:0;max-width:none;width:100%}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--panel-root,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--panel-root{border-radius:0}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--panel-root>*,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--panel-root>*{display:none}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--drop-label,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--drop-label{align-items:center;bottom:0;display:flex;height:auto;justify-content:center;z-index:7}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--item-panel,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--item-panel{display:none}.custom-fields-component .filepond--root[data-style-panel-layout~=compact] .filepond--list-scroller,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--list-scroller{height:100%;margin-bottom:0;margin-top:0;overflow:hidden}.custom-fields-component .filepond--root[data-style-panel-layout~=compact] .filepond--list,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--list{height:100%;left:0;right:0}.custom-fields-component .filepond--root[data-style-panel-layout~=compact] .filepond--item,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--item{margin:0}.custom-fields-component .filepond--root[data-style-panel-layout~=compact] .filepond--file-wrapper,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--file-wrapper{height:100%}.custom-fields-component .filepond--root[data-style-panel-layout~=compact] .filepond--drop-label,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--drop-label{z-index:7}.custom-fields-component .filepond--root[data-style-panel-layout~=circle]{border-radius:99999rem;overflow:hidden}.custom-fields-component .filepond--root[data-style-panel-layout~=circle]>.filepond--panel{border-radius:inherit}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--file-info,.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--file-status,.custom-fields-component .filepond--root[data-style-panel-layout~=circle]>.filepond--panel>*{display:none}@media not all and (min-resolution:.001dpcm){@supports ((-webkit-appearance:none)) and (stroke-color:transparent){.custom-fields-component .filepond--root[data-style-panel-layout~=circle]{will-change:transform}}}.custom-fields-component .filepond--panel-root{background-color:#f1f0ef;border-radius:.5em}.custom-fields-component .filepond--panel{height:100%!important;left:0;margin:0;pointer-events:none;position:absolute;right:0;top:0}.custom-fields-component .filepond-panel:not([data-scalable=false]){height:auto!important}.custom-fields-component .filepond--panel[data-scalable=false]>div{display:none}.custom-fields-component .filepond--panel[data-scalable=true]{background-color:#0000!important;border:none!important;-webkit-transform-style:preserve-3d;transform-style:preserve-3d}.custom-fields-component .filepond--panel-bottom,.custom-fields-component .filepond--panel-center,.custom-fields-component .filepond--panel-top{left:0;margin:0;padding:0;position:absolute;right:0;top:0}.custom-fields-component .filepond--panel-bottom,.custom-fields-component .filepond--panel-top{height:.5em}.custom-fields-component .filepond--panel-top{border-bottom:none!important;border-bottom-left-radius:0!important;border-bottom-right-radius:0!important}.custom-fields-component .filepond--panel-top:after{background-color:inherit;bottom:-1px;content:"";height:2px;left:0;position:absolute;right:0}.custom-fields-component .filepond--panel-bottom,.custom-fields-component .filepond--panel-center{backface-visibility:hidden;transform:translateY(.5em);transform-origin:0 0;will-change:transform}.custom-fields-component .filepond--panel-bottom{border-top:none!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.custom-fields-component .filepond--panel-bottom:before{background-color:inherit;content:"";height:2px;left:0;position:absolute;right:0;top:-1px}.custom-fields-component .filepond--panel-center{border-bottom:none!important;border-radius:0!important;border-top:none!important;height:100px!important}.custom-fields-component .filepond--panel-center:not([style]){visibility:hidden}.custom-fields-component .filepond--progress-indicator{color:#fff;height:1.25em;margin:0;pointer-events:none;position:static;width:1.25em;will-change:transform,opacity}.custom-fields-component .filepond--progress-indicator svg{height:100%;transform-box:fill-box;vertical-align:top;width:100%}.custom-fields-component .filepond--progress-indicator path{fill:none;stroke:currentColor}.custom-fields-component .filepond--list-scroller{z-index:6}.custom-fields-component .filepond--drop-label{z-index:5}.custom-fields-component .filepond--drip{z-index:3}.custom-fields-component .filepond--root>.filepond--panel{z-index:2}.custom-fields-component .filepond--browser{z-index:1}.custom-fields-component .filepond--root{box-sizing:border-box;contain:layout style size;direction:ltr;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:1rem;font-weight:450;line-height:normal;margin-bottom:1em;position:relative;text-align:left;text-rendering:optimizeLegibility}.custom-fields-component .filepond--root *{box-sizing:inherit;line-height:inherit}.custom-fields-component .filepond--root :not(text){font-size:inherit}.custom-fields-component .filepond--root[data-disabled]{pointer-events:none}.custom-fields-component .filepond--root[data-disabled] .filepond--list-scroller{pointer-events:all}.custom-fields-component .filepond--root[data-disabled] .filepond--list{pointer-events:none}.custom-fields-component .filepond--root .filepond--drop-label{min-height:4.75em}.custom-fields-component .filepond--root .filepond--list-scroller{margin-bottom:1em;margin-top:1em}.custom-fields-component .filepond--root .filepond--credits{bottom:-14px;color:inherit;font-size:11px;line-height:.85;opacity:.4;position:absolute;right:0;text-decoration:none;z-index:3}.custom-fields-component .filepond--root .filepond--credits[style]{bottom:auto;margin-top:14px;top:0}.custom-fields-component .filepond--action-edit-item.filepond--action-edit-item{height:2em;padding:.1875em;width:2em}.custom-fields-component .filepond--action-edit-item.filepond--action-edit-item[data-align*=center]{margin-left:-.1875em}.custom-fields-component .filepond--action-edit-item.filepond--action-edit-item[data-align*=bottom]{margin-bottom:-.1875em}.custom-fields-component .filepond--action-edit-item-alt{background:0 0;border:none;color:inherit;font-family:inherit;line-height:inherit;margin:0 0 0 .25em;outline:none;padding:0;pointer-events:all;position:absolute}.custom-fields-component .filepond--action-edit-item-alt svg{height:1.3125em;width:1.3125em}.custom-fields-component .filepond--action-edit-item-alt span{font-size:0;opacity:0}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--action-edit-item{opacity:1!important;visibility:visible!important}.custom-fields-component .filepond--image-preview-markup{left:0;position:absolute;top:0}.custom-fields-component .filepond--image-preview-wrapper{z-index:2}.custom-fields-component .filepond--image-preview-overlay{display:block;left:0;margin:0;max-height:7rem;min-height:5rem;opacity:0;pointer-events:none;position:absolute;top:0;-webkit-user-select:none;user-select:none;width:100%;z-index:2}.custom-fields-component .filepond--image-preview-overlay svg{color:inherit;height:auto;max-height:inherit;width:100%}.custom-fields-component .filepond--image-preview-overlay-idle{color:#282828d9;mix-blend-mode:multiply}.custom-fields-component .filepond--image-preview-overlay-success{color:#369763;mix-blend-mode:normal}.custom-fields-component .filepond--image-preview-overlay-failure{color:#c44e47;mix-blend-mode:normal}@supports (-webkit-marquee-repetition:infinite) and ((-o-object-fit:fill) or (object-fit:fill)){.custom-fields-component .filepond--image-preview-overlay-idle{mix-blend-mode:normal}}.custom-fields-component .filepond--image-preview-wrapper{background:#00000003;border-radius:.45em;height:100%;left:0;margin:0;overflow:hidden;position:absolute;right:0;top:0;-webkit-user-select:none;user-select:none}.custom-fields-component .filepond--image-preview{align-items:center;background:#222;display:flex;height:100%;left:0;pointer-events:none;position:absolute;top:0;width:100%;will-change:transform,opacity;z-index:1}.custom-fields-component .filepond--image-clip{margin:0 auto;overflow:hidden;position:relative}.custom-fields-component .filepond--image-clip[data-transparency-indicator=grid] canvas,.custom-fields-component .filepond--image-clip[data-transparency-indicator=grid] img{background-color:#fff;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23eee' viewBox='0 0 100 100'%3E%3Cpath d='M0 0h50v50H0M50 50h50v50H50'/%3E%3C/svg%3E");background-size:1.25em 1.25em}.custom-fields-component .filepond--image-bitmap,.custom-fields-component .filepond--image-vector{left:0;position:absolute;top:0;will-change:transform}.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview-wrapper{border-radius:0}.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview{align-items:center;display:flex;height:100%;justify-content:center}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-wrapper{border-radius:99999rem}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-overlay{bottom:0;top:auto;transform:scaleY(-1)}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--file .filepond--file-action-button[data-align*=bottom]:not([data-align*=center]){margin-bottom:.325em}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=left]{left:calc(50% - 3em)}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=right]{right:calc(50% - 3em)}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=left],.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=right]{margin-bottom:.5125em}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=center]{margin-bottom:.1875em;margin-left:.1875em;margin-top:0}.custom-fields-component .filepond--media-preview audio{display:none}.custom-fields-component .filepond--media-preview .audioplayer{margin:2.3em auto auto;width:calc(100% - 1.4em)}.custom-fields-component .filepond--media-preview .playpausebtn{background-position:50%;background-repeat:no-repeat;border:none;border-radius:25px;cursor:pointer;float:left;height:25px;margin-right:.3em;margin-top:.3em;outline:none;width:25px}.custom-fields-component .filepond--media-preview .playpausebtn:hover{background-color:#00000080}.custom-fields-component .filepond--media-preview .play{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAyElEQVQ4T9XUsWoCQRRG4XPaFL5SfIy8gKYKBCysrax8Ahs7qzQ2qVIFOwsrsbEWLEK6EBFGBrIQhN2d3dnGgalm+Jh7789Ix8uOPe4YDCH0gZ66atKW0pJDCE/AEngDXtRjCpwCRucbGANzNVTBqWBhfAJDdV+GNgWj8wtM41bPt3AbsDB2f69d/0dzwC0wUDe54A8wAWbqJbfkD+BZPeQO5QsYqYu6LKb0MIb7VT3VYfG8CnwEHtT3FKi4c8e/TZMyk3LYFrwCgMdHFbRDKS8AAAAASUVORK5CYII=)}.custom-fields-component .filepond--media-preview .pause{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAh0lEQVQ4T+2UsQkCURBE30PLMbAMMResQrAPsQ0TK9AqDKxGZeTLD74aGNwlhzfZssvADDMrPcOe+RggYZIJcG2s2KinMidZAvu6u6uzT8u+JCeZArfmcKUeK+EaONTdQy23bxgJX8aPHvIHsSnVuzTx36rn2pQFsGuqN//ZlK7vbIDvq6vkJ9yteBXzecYbAAAAAElFTkSuQmCC)}.custom-fields-component .filepond--media-preview .timeline{background:#ffffff4d;border-radius:15px;float:left;height:3px;margin-top:1em;width:calc(100% - 2.5em)}.custom-fields-component .filepond--media-preview .playhead{background:#fff;border-radius:50%;height:13px;margin-top:-5px;width:13px}.custom-fields-component .filepond--media-preview-wrapper{background:#00000003;border-radius:.45em;height:100%;left:0;margin:0;overflow:hidden;pointer-events:auto;position:absolute;right:0;top:0}.custom-fields-component .filepond--media-preview-wrapper:before{background:linear-gradient(#000,#0000);content:" ";filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#000000",endColorstr="#00000000",GradientType=0);height:2em;position:absolute;width:100%;z-index:3}.custom-fields-component .filepond--media-preview{display:block;height:100%;position:relative;transform-origin:50%;width:100%;will-change:transform,opacity;z-index:1}.custom-fields-component .filepond--media-preview audio,.custom-fields-component .filepond--media-preview video{width:100%;will-change:transform}.custom-fields-component .noUi-target,.custom-fields-component .noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:#0000;box-sizing:border-box;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;user-select:none}.custom-fields-component .noUi-target{position:relative}.custom-fields-component .noUi-base,.custom-fields-component .noUi-connects{height:100%;position:relative;width:100%;z-index:1}.custom-fields-component .noUi-connects{overflow:hidden;z-index:0}.custom-fields-component .noUi-connect,.custom-fields-component .noUi-origin{height:100%;position:absolute;right:0;top:0;transform-origin:0 0;-webkit-transform-style:preserve-3d;transform-style:flat;width:100%;will-change:transform;z-index:1}.custom-fields-component .noUi-txt-dir-rtl.noUi-horizontal .noUi-origin{left:0;right:auto}.custom-fields-component .noUi-vertical .noUi-origin{top:-100%;width:0}.custom-fields-component .noUi-horizontal .noUi-origin{height:0}.custom-fields-component .noUi-handle{backface-visibility:hidden;position:absolute}.custom-fields-component .noUi-touch-area{height:100%;width:100%}.custom-fields-component .noUi-state-tap .noUi-connect,.custom-fields-component .noUi-state-tap .noUi-origin{transition:transform .3s}.custom-fields-component .noUi-state-drag *{cursor:inherit!important}.custom-fields-component .noUi-horizontal{height:18px}.custom-fields-component .noUi-horizontal .noUi-handle{height:28px;right:-17px;top:-6px;width:34px}.custom-fields-component .noUi-vertical{width:18px}.custom-fields-component .noUi-vertical .noUi-handle{bottom:-17px;height:34px;right:-6px;width:28px}.custom-fields-component .noUi-txt-dir-rtl.noUi-horizontal .noUi-handle{left:-17px;right:auto}.custom-fields-component .noUi-target{background:#fafafa;border:1px solid #d3d3d3;border-radius:4px;box-shadow:inset 0 1px 1px #f0f0f0,0 3px 6px -5px #bbb}.custom-fields-component .noUi-connects{border-radius:3px}.custom-fields-component .noUi-connect{background:#3fb8af}.custom-fields-component .noUi-draggable{cursor:ew-resize}.custom-fields-component .noUi-vertical .noUi-draggable{cursor:ns-resize}.custom-fields-component .noUi-handle{background:#fff;border:1px solid #d9d9d9;border-radius:3px;box-shadow:inset 0 0 1px #fff,inset 0 1px 7px #ebebeb,0 3px 6px -3px #bbb;cursor:default}.custom-fields-component .noUi-active{box-shadow:inset 0 0 1px #fff,inset 0 1px 7px #ddd,0 3px 6px -3px #bbb}.custom-fields-component .noUi-handle:after,.custom-fields-component .noUi-handle:before{background:#e8e7e6;content:"";display:block;height:14px;left:14px;position:absolute;top:6px;width:1px}.custom-fields-component .noUi-handle:after{left:17px}.custom-fields-component .noUi-vertical .noUi-handle:after,.custom-fields-component .noUi-vertical .noUi-handle:before{height:1px;left:6px;top:14px;width:14px}.custom-fields-component .noUi-vertical .noUi-handle:after{top:17px}.custom-fields-component [disabled] .noUi-connect{background:#b8b8b8}.custom-fields-component [disabled] .noUi-handle,.custom-fields-component [disabled].noUi-handle,.custom-fields-component [disabled].noUi-target{cursor:not-allowed}.custom-fields-component .noUi-pips,.custom-fields-component .noUi-pips *{box-sizing:border-box}.custom-fields-component .noUi-pips{color:#999;position:absolute}.custom-fields-component .noUi-value{position:absolute;text-align:center;white-space:nowrap}.custom-fields-component .noUi-value-sub{color:#ccc;font-size:10px}.custom-fields-component .noUi-marker{background:#ccc;position:absolute}.custom-fields-component .noUi-marker-large,.custom-fields-component .noUi-marker-sub{background:#aaa}.custom-fields-component .noUi-pips-horizontal{height:80px;left:0;padding:10px 0;top:100%;width:100%}.custom-fields-component .noUi-value-horizontal{transform:translate(-50%,50%)}.custom-fields-component .noUi-rtl .noUi-value-horizontal{transform:translate(50%,50%)}.custom-fields-component .noUi-marker-horizontal.noUi-marker{height:5px;margin-left:-1px;width:2px}.custom-fields-component .noUi-marker-horizontal.noUi-marker-sub{height:10px}.custom-fields-component .noUi-marker-horizontal.noUi-marker-large{height:15px}.custom-fields-component .noUi-pips-vertical{height:100%;left:100%;padding:0 10px;top:0}.custom-fields-component .noUi-value-vertical{padding-left:25px;transform:translateY(-50%)}.custom-fields-component .noUi-rtl .noUi-value-vertical{transform:translateY(50%)}.custom-fields-component .noUi-marker-vertical.noUi-marker{height:2px;margin-top:-1px;width:5px}.custom-fields-component .noUi-marker-vertical.noUi-marker-sub{width:10px}.custom-fields-component .noUi-marker-vertical.noUi-marker-large{width:15px}.custom-fields-component .noUi-tooltip{background:#fff;border:1px solid #d9d9d9;border-radius:3px;color:#000;display:block;padding:5px;position:absolute;text-align:center;white-space:nowrap}.custom-fields-component .noUi-horizontal .noUi-tooltip{bottom:120%;left:50%;transform:translate(-50%)}.custom-fields-component .noUi-vertical .noUi-tooltip{right:120%;top:50%;transform:translateY(-50%)}.custom-fields-component .noUi-horizontal .noUi-origin>.noUi-tooltip{bottom:10px;left:auto;transform:translate(50%)}.custom-fields-component .noUi-vertical .noUi-origin>.noUi-tooltip{right:28px;top:auto;transform:translateY(-18px)}.custom-fields-component .fi-fo-builder{display:grid;grid-template-columns:repeat(1,minmax(0,1fr));row-gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-builder .fi-fo-builder-actions{column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-builder .fi-fo-builder-actions.fi-hidden{display:none}.custom-fields-component .fi-fo-builder .fi-fo-builder-items{display:grid;grid-template-columns:repeat(1,minmax(0,1fr))}.custom-fields-component .fi-fo-builder .fi-fo-builder-items>*+*{margin-top:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item{background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-item:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-item:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-item:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-item.fi-collapsed .fi-fo-builder-item-header-collapsible-actions{rotate:-180deg}.custom-fields-component .fi-fo-builder .fi-fo-builder-item.fi-collapsed .fi-fo-builder-item-header-collapse-action,.custom-fields-component .fi-fo-builder .fi-fo-builder-item:not(.fi-collapsed) .fi-fo-builder-item-header-expand-action{opacity:0;pointer-events:none}.custom-fields-component .fi-fo-builder.fi-fo-builder-not-contained>.fi-fo-builder-items>.fi-fo-builder-item{--tw-shadow:0 0 #0000;--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);background-color:#0000;border-radius:0;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-fo-builder.fi-fo-builder-not-contained>.fi-fo-builder-items>.fi-fo-builder-item>.fi-fo-builder-item-content{padding:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-builder.fi-fo-builder-not-contained>.fi-fo-builder-items>.fi-fo-builder-label-between-items-ctn>.fi-fo-builder-label-between-items-divider-before{width:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-builder.fi-fo-builder-not-contained>.fi-fo-builder-items>.fi-fo-builder-label-between-items-ctn>.fi-fo-builder-label-between-items{padding-inline-start:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;overflow:hidden;padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-builder.fi-collapsible .fi-fo-builder-item-header{cursor:pointer;-webkit-user-select:none;user-select:none}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-start-actions{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-icon{color:var(--gray-400)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-label.fi-truncated{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-end-actions{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;margin-inline-start:auto}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-collapsible-actions{position:relative}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-collapse-action,.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-collapsible-actions,.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-expand-action{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-expand-action{inset:calc(var(--spacing)*0);position:absolute;rotate:180deg}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-content:not(.fi-fo-builder-item-content-has-preview){padding:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-content.fi-fo-builder-item-content-has-preview{position:relative}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-has-header>.fi-fo-builder-item-content{border-color:var(--gray-100);border-top-style:var(--tw-border-style);border-top-width:1px}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-has-header>.fi-fo-builder-item-content:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-item-has-header>.fi-fo-builder-item-content:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-preview:not(.fi-interactive){pointer-events:none}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-preview-edit-overlay{cursor:pointer;inset:calc(var(--spacing)*0);position:absolute;z-index:1}.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker-ctn{background-color:var(--color-white);border-radius:var(--radius-lg)}.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker-ctn:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items-ctn{display:flex;height:calc(var(--spacing)*0);justify-content:center;margin-top:calc(var(--spacing)*0);opacity:0;overflow:visible;pointer-events:none;position:relative;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));visibility:hidden;width:100%}.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items-ctn:focus-within,.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items-ctn:has(+.fi-fo-builder-item:hover),.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items-ctn:hover,.custom-fields-component .fi-fo-builder .fi-fo-builder-item:hover+.fi-fo-builder-add-between-items-ctn{opacity:1;pointer-events:auto;visibility:visible}.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items{z-index:10;--tw-translate-y:calc(-50% + 0.5rem);background-color:var(--color-white);border-radius:var(--radius-lg);position:absolute;top:50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-ctn{align-items:center;display:flex;margin-bottom:calc(var(--spacing)*-3);margin-top:calc(var(--spacing)*1);position:relative}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-before{border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:1px;flex-shrink:0;width:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-before:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-before:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-inline:calc(var(--spacing)*1);--tw-font-weight:var(--font-weight-medium);color:var(--gray-500);flex-shrink:0;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-after{border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:1px;flex:1}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-after:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-after:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker{display:flex;justify-content:center}.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker.fi-align-left,.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker.fi-align-end,.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-search-input-wrp{margin-bottom:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-actions{margin-bottom:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-options{gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-options.fi-grid-direction-col{margin-top:calc(var(--spacing)*-4)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-options.fi-grid-direction-col .fi-fo-checkbox-list-option-ctn{break-inside:avoid;padding-top:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option{column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-checkbox-input{flex-shrink:0;margin-top:calc(var(--spacing)*1)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-fo-checkbox-list-option-text{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);display:grid;line-height:calc(var(--spacing)*6)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-fo-checkbox-list-option-label{--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium);overflow:hidden;overflow-wrap:break-word}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-fo-checkbox-list-option-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-fo-checkbox-list-option-description{color:var(--gray-500)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-fo-checkbox-list-option-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-no-search-results-message{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-no-search-results-message:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-code-editor{overflow:hidden}.custom-fields-component .fi-fo-code-editor .cm-editor.cm-focused{--tw-outline-style:none!important;outline-style:none!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters{background-color:var(--gray-100)!important;border-inline-end-color:var(--gray-300)!important;min-height:calc(var(--spacing)*48)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters:where(.dark,.dark *){background-color:var(--gray-950)!important;border-inline-end-color:var(--gray-800)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters .cm-gutter.cm-lineNumbers .cm-gutterElement{border-end-start-radius:var(--radius-md);border-start-start-radius:var(--radius-md);margin-inline-start:calc(var(--spacing)*1)}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters .cm-gutter.cm-lineNumbers .cm-gutterElement.cm-activeLineGutter{background-color:var(--gray-200)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters .cm-gutter.cm-lineNumbers .cm-gutterElement.cm-activeLineGutter:where(.dark,.dark *){background-color:var(--gray-800)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters .cm-gutter.cm-foldGutter .cm-gutterElement.cm-activeLineGutter{background-color:var(--gray-200)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters .cm-gutter.cm-foldGutter .cm-gutterElement.cm-activeLineGutter:where(.dark,.dark *){background-color:var(--gray-800)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-scroller{min-height:calc(var(--spacing)*48)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-line{border-end-end-radius:var(--radius-md);border-start-end-radius:var(--radius-md);margin-inline-end:calc(var(--spacing)*1)}.custom-fields-component .fi-fo-code-editor.fi-disabled .cm-editor .cm-gutters .cm-gutter.cm-foldGutter .cm-gutterElement.cm-activeLineGutter,.custom-fields-component .fi-fo-code-editor.fi-disabled .cm-editor .cm-gutters .cm-gutter.cm-lineNumbers .cm-gutterElement.cm-activeLineGutter,.custom-fields-component .fi-fo-code-editor.fi-disabled .cm-editor .cm-line.cm-activeLine{background-color:#0000!important}.custom-fields-component .fi-fo-color-picker .fi-input-wrp-content{display:flex}.custom-fields-component .fi-fo-color-picker .fi-fo-color-picker-preview{border-radius:3.40282e+38px;flex-shrink:0;height:calc(var(--spacing)*5);margin-block:auto;margin-inline-end:calc(var(--spacing)*3);-webkit-user-select:none;user-select:none;width:calc(var(--spacing)*5)}.custom-fields-component .fi-fo-color-picker .fi-fo-color-picker-preview.fi-empty{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-200);--tw-ring-inset:inset}.custom-fields-component .fi-fo-color-picker .fi-fo-color-picker-preview.fi-empty:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-color-picker .fi-fo-color-picker-preview.fi-empty:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-color-picker .fi-fo-color-picker-panel{border-radius:var(--radius-lg);z-index:10;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);display:none;position:absolute}.custom-fields-component .fi-fo-date-time-picker input::-webkit-datetime-edit{display:block;padding:0}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-trigger{width:100%}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input{--tw-border-style:none;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*3);width:100%;--tw-leading:calc(var(--spacing)*6);color:var(--gray-950);line-height:calc(var(--spacing)*6);--tw-outline-style:none;background-color:#0000;border-style:none;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input::placeholder{color:var(--gray-400)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input:disabled{color:var(--gray-500);-webkit-text-fill-color:var(--color-gray-500)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input:where(.dark,.dark *)::placeholder{color:var(--gray-500)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input:where(.dark,.dark *):disabled{color:var(--gray-400);-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel{position:absolute;z-index:10}.custom-fields-component :where(.fi-fo-date-time-picker .fi-fo-date-time-picker-panel>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*3*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*3*var(--tw-space-y-reverse))}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel{background-color:var(--color-white);border-radius:var(--radius-lg);padding:calc(var(--spacing)*4);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel .fi-fo-date-time-picker-panel-header{align-items:center;display:flex;justify-content:space-between}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-month-select{cursor:pointer;--tw-border-style:none;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding:calc(var(--spacing)*0);--tw-font-weight:var(--font-weight-medium);background-color:#0000;border-style:none;color:var(--gray-950);flex-grow:1;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-month-select:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-month-select:where(.dark,.dark *){background-color:var(--gray-900);color:var(--color-white)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-year-input{width:calc(var(--spacing)*16);--tw-border-style:none;background-color:#0000;border-style:none;color:var(--gray-950);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding:calc(var(--spacing)*0);text-align:right}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-year-input:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-year-input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar-header{display:grid;gap:calc(var(--spacing)*1);grid-template-columns:repeat(7,minmax(0,1fr))}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar-header .fi-fo-date-time-picker-calendar-header-day{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));text-align:center;--tw-font-weight:var(--font-weight-medium);color:var(--gray-500);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar-header .fi-fo-date-time-picker-calendar-header-day:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar{display:grid;gap:calc(var(--spacing)*1);grid-template-columns:repeat(7,minmax(calc(var(--spacing)*7),1fr))}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));text-align:center;--tw-leading:var(--leading-loose);line-height:var(--leading-loose);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;border-radius:3.40282e+38px;transition-duration:75ms}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-disabled{opacity:.5;pointer-events:none}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day:not(.fi-disabled){cursor:pointer}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-selected{background-color:var(--gray-50);color:var(--primary-600)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-selected:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-selected:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-selected:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-focused:not(.fi-selected):not(.fi-disabled){background-color:var(--gray-100)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-focused:not(.fi-selected):not(.fi-disabled):where(.dark,.dark *){background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-focused:not(.fi-selected):not(.fi-disabled):where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-fo-date-time-picker-calendar-day-today:not(.fi-focused):not(.fi-selected):not(.fi-disabled){color:var(--primary-600)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-fo-date-time-picker-calendar-day-today:not(.fi-focused):not(.fi-selected):not(.fi-disabled):where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day:not(.fi-fo-date-time-picker-calendar-day-today):not(.fi-selected){color:var(--gray-950)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day:not(.fi-fo-date-time-picker-calendar-day-today):not(.fi-selected):where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs{align-items:center;display:flex;justify-content:center}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs:where(:dir(rtl),[dir=rtl],[dir=rtl] *){flex-direction:row-reverse}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs input{width:calc(var(--spacing)*10);--tw-border-style:none;background-color:#0000;border-style:none;color:var(--gray-950);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));margin-inline-end:calc(var(--spacing)*1);padding:calc(var(--spacing)*0);text-align:center}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs input:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs .fi-fo-date-time-picker-time-input-separator{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-500);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs .fi-fo-date-time-picker-time-input-separator:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-field{display:grid;row-gap:calc(var(--spacing)*2)}@media (min-width:40rem){.custom-fields-component .fi-fo-field.fi-fo-field-has-inline-label{align-items:flex-start;column-gap:calc(var(--spacing)*4);grid-template-columns:repeat(3,minmax(0,1fr))}.custom-fields-component .fi-fo-field.fi-fo-field-has-inline-label .fi-fo-field-content-col{grid-column:span 2/span 2}}.custom-fields-component .fi-fo-field .fi-fo-field-label,.custom-fields-component .fi-fo-field .fi-fo-field-label-ctn{align-items:flex-start;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component :is(.fi-fo-field .fi-fo-field-label-ctn,.fi-fo-field .fi-fo-field-label)>.fi-checkbox-input{flex-shrink:0;margin-top:calc(var(--spacing)*.5)}.custom-fields-component :is(.fi-fo-field .fi-fo-field-label-ctn,.fi-fo-field .fi-fo-field-label)>.fi-toggle{margin-block:calc(var(--spacing)*-.5)}.custom-fields-component :is(.fi-fo-field .fi-fo-field-label-ctn,.fi-fo-field .fi-fo-field-label)>.fi-sc:first-child{flex-grow:0}.custom-fields-component :is(.fi-fo-field .fi-fo-field-label-ctn,.fi-fo-field .fi-fo-field-label).fi-hidden{display:none}.custom-fields-component .fi-fo-field .fi-fo-field-label-content{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-field .fi-fo-field-label-content:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-field .fi-fo-field-label-content .fi-fo-field-label-required-mark{--tw-font-weight:var(--font-weight-medium);color:var(--danger-600);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-field .fi-fo-field-label-content .fi-fo-field-label-required-mark:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-fo-field .fi-fo-field-label-col{display:grid;grid-auto-columns:minmax(0,1fr);height:100%;row-gap:calc(var(--spacing)*2)}@media (min-width:40rem){.custom-fields-component .fi-fo-field .fi-fo-field-label-col.fi-vertical-align-start{align-items:flex-start}.custom-fields-component .fi-fo-field .fi-fo-field-label-col.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-fo-field .fi-fo-field-label-col.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-fo-field .fi-fo-field-content-col{display:grid;grid-auto-columns:minmax(0,1fr);row-gap:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-field .fi-fo-field-content-ctn{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;width:100%}.custom-fields-component .fi-fo-field .fi-fo-field-content{width:100%}.custom-fields-component .fi-fo-field .fi-fo-field-wrp-error-message{color:var(--danger-600);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-fo-field .fi-fo-field-wrp-error-message:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-fo-field .fi-fo-field-wrp-error-list{list-style-position:inside;list-style-type:disc}.custom-fields-component :where(.fi-fo-field .fi-fo-field-wrp-error-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*.5*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*.5*var(--tw-space-y-reverse))}.custom-fields-component .fi-fo-file-upload{display:flex;flex-direction:column;row-gap:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-file-upload.fi-align-left,.custom-fields-component .fi-fo-file-upload.fi-align-start{align-items:flex-start}.custom-fields-component .fi-fo-file-upload.fi-align-center{align-items:center}.custom-fields-component .fi-fo-file-upload.fi-align-end,.custom-fields-component .fi-fo-file-upload.fi-align-right{align-items:flex-end}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-input-ctn{height:100%;width:100%}.custom-fields-component .fi-fo-file-upload.fi-fo-file-upload-avatar .fi-fo-file-upload-input-ctn{height:100%;width:calc(var(--spacing)*32)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-error-message{color:var(--danger-600);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-error-message:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-fo-file-upload .filepond--root{background-color:var(--color-white);border-radius:var(--radius-lg);font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";margin-bottom:calc(var(--spacing)*0);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);overflow:hidden}.custom-fields-component .fi-fo-file-upload .filepond--root:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--root:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-file-upload .filepond--root:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--root:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-fo-file-upload .filepond--root[data-disabled=disabled]{background-color:var(--gray-50)}.custom-fields-component .fi-fo-file-upload .filepond--root[data-disabled=disabled]:where(.dark,.dark *){--tw-ring-color:#ffffff1a;background-color:#0000}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--root[data-disabled=disabled]:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-file-upload .filepond--root[data-style-panel-layout=compact\ circle]{border-radius:3.40282e+38px}.custom-fields-component .fi-fo-file-upload .filepond--panel-root{background-color:#0000}.custom-fields-component .fi-fo-file-upload .filepond--drop-label label{color:var(--gray-600);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding:calc(var(--spacing)*3)!important}.custom-fields-component .fi-fo-file-upload .filepond--drop-label label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-file-upload .filepond--label-action{--tw-font-weight:var(--font-weight-medium);color:var(--primary-600);font-weight:var(--font-weight-medium);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;text-decoration-line:none;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-fo-file-upload .filepond--label-action:hover{color:var(--primary-500)}}.custom-fields-component .fi-fo-file-upload .filepond--label-action:where(.dark,.dark *){color:var(--color-white)}@media (hover:hover){.custom-fields-component .fi-fo-file-upload .filepond--label-action:where(.dark,.dark *):hover{color:var(--primary-500)}}.custom-fields-component .fi-fo-file-upload .filepond--drip-blob{background-color:var(--gray-400)}.custom-fields-component .fi-fo-file-upload .filepond--drip-blob:where(.dark,.dark *){background-color:var(--gray-500)}.custom-fields-component .fi-fo-file-upload .filepond--root[data-style-panel-layout=grid] .filepond--item{display:inline;width:calc(50% - .5rem)}@media (min-width:64rem){.custom-fields-component .fi-fo-file-upload .filepond--root[data-style-panel-layout=grid] .filepond--item{width:calc(33.33% - .5rem)}}.custom-fields-component .fi-fo-file-upload .filepond--download-icon{background-color:var(--color-white);display:inline-block;height:calc(var(--spacing)*4);margin-inline-end:calc(var(--spacing)*1);pointer-events:auto;vertical-align:bottom;width:calc(var(--spacing)*4)}@media (hover:hover){.custom-fields-component .fi-fo-file-upload .filepond--download-icon:hover{background-color:#ffffffb3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--download-icon:hover{background-color:color-mix(in oklab,var(--color-white)70%,transparent)}}}.custom-fields-component .fi-fo-file-upload .filepond--download-icon{-webkit-mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWRvd25sb2FkIj48cGF0aCBkPSJNMjEgMTV2NGEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMnYtNE03IDEwbDUgNSA1LTVNMTIgMTVWMyIvPjwvc3ZnPg==);mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWRvd25sb2FkIj48cGF0aCBkPSJNMjEgMTV2NGEyIDIgMCAwIDEtMiAySDVhMiAyIDAgMCAxLTItMnYtNE03IDEwbDUgNSA1LTVNMTIgMTVWMyIvPjwvc3ZnPg==);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:100%;mask-size:100%}.custom-fields-component .fi-fo-file-upload .filepond--open-icon{background-color:var(--color-white);display:inline-block;height:calc(var(--spacing)*4);margin-inline-end:calc(var(--spacing)*1);pointer-events:auto;vertical-align:bottom;width:calc(var(--spacing)*4)}@media (hover:hover){.custom-fields-component .fi-fo-file-upload .filepond--open-icon:hover{background-color:#ffffffb3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--open-icon:hover{background-color:color-mix(in oklab,var(--color-white)70%,transparent)}}}.custom-fields-component .fi-fo-file-upload .filepond--open-icon{-webkit-mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiIGNsYXNzPSJoLTYgdy02IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTEwIDZINmEyIDIgMCAwIDAtMiAydjEwYTIgMiAwIDAgMCAyIDJoMTBhMiAyIDAgMCAwIDItMnYtNE0xNCA0aDZtMCAwdjZtMC02TDEwIDE0Ii8+PC9zdmc+);mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiIGNsYXNzPSJoLTYgdy02IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTEwIDZINmEyIDIgMCAwIDAtMiAydjEwYTIgMiAwIDAgMCAyIDJoMTBhMiAyIDAgMCAwIDItMnYtNE0xNCA0aDZtMCAwdjZtMC02TDEwIDE0Ii8+PC9zdmc+);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:100%;mask-size:100%}.custom-fields-component .fi-fo-file-upload .filepond--file-action-button.filepond--action-edit-item{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--file-action-button.filepond--action-edit-item{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor{height:100dvh;inset:calc(var(--spacing)*0);isolation:isolate;padding:calc(var(--spacing)*2);position:fixed;width:100vw;z-index:50}@media (min-width:40rem){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor{padding:calc(var(--spacing)*10)}}@media (min-width:48rem){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor{padding:calc(var(--spacing)*20)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-overlay{background-color:var(--gray-950);cursor:pointer;height:100%;inset:calc(var(--spacing)*0);position:fixed;width:100%}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-overlay{background-color:color-mix(in oklab,var(--gray-950)50%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-overlay:where(.dark,.dark *){background-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-overlay:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-950)75%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-overlay{will-change:transform}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-window{background-color:var(--color-white);border-radius:var(--radius-xl);isolation:isolate;--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);height:100%;width:100%;--tw-ring-color:var(--gray-900);display:flex;flex-direction:column;margin-inline:auto;overflow:hidden}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-window{--tw-ring-color:color-mix(in oklab,var(--gray-900)10%,transparent)}}@media (min-width:64rem){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-window{flex-direction:row}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-window:where(.dark,.dark *){background-color:var(--gray-800);--tw-ring-color:var(--gray-50)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-window:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--gray-50)10%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-image-ctn{flex:1;margin:calc(var(--spacing)*4);max-height:100%;max-width:100%}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-image{height:100%;width:auto}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel{background-color:var(--gray-50);display:flex;flex:1;flex-direction:column;height:100%;overflow-y:auto;width:100%}@media (min-width:64rem){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel{max-width:var(--container-xs)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900)30%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-main{flex:1}.custom-fields-component :where(.fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-main>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*6*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*6*var(--tw-space-y-reverse))}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-main{overflow:auto;padding:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group{display:grid;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group .fi-btn-group{width:100%}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group .fi-btn.fi-active{background-color:var(--gray-50)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group .fi-btn.fi-active:where(.dark,.dark *){background-color:var(--gray-700)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group .fi-fo-file-upload-editor-control-panel-group-title{color:var(--gray-950);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group .fi-fo-file-upload-editor-control-panel-group-title:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-footer{align-items:center;display:flex;gap:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-reset-action{margin-left:auto}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .cropper-drag-box.cropper-crop.cropper-modal{background-color:var(--gray-100)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .cropper-drag-box.cropper-crop.cropper-modal{background-color:color-mix(in oklab,var(--gray-100)50%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .cropper-drag-box.cropper-crop.cropper-modal{opacity:1}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .cropper-drag-box.cropper-crop.cropper-modal:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .cropper-drag-box.cropper-crop.cropper-modal:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900)80%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-circle-cropper .cropper-face,.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-circle-cropper .cropper-view-box{border-radius:50%}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-crop-only .fi-fo-file-upload-editor-window{flex-direction:column;max-width:var(--container-3xl)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-crop-only .fi-fo-file-upload-editor-image-ctn{flex:1;min-height:calc(var(--spacing)*0);overflow:hidden}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-crop-only .fi-fo-file-upload-editor-control-panel{flex:none;height:auto}@media (min-width:64rem){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-crop-only .fi-fo-file-upload-editor-control-panel{max-width:none}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-crop-only .fi-fo-file-upload-editor-control-panel-footer{justify-content:flex-start}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table-ctn>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table{table-layout:auto;width:100%}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>thead>tr>th{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3);text-align:start;--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>thead>tr>th:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>thead>tr>th.fi-has-action{padding:calc(var(--spacing)*0);width:calc(var(--spacing)*9)}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody>tr>:not(:last-child)){--tw-divide-x-reverse:0;border-color:var(--gray-200);border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style)}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody>tr:where(:dir(rtl),[dir=rtl],[dir=rtl] *)>:not(:last-child)){--tw-divide-x-reverse:1}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody>tr:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody>tr:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>tbody>tr>td{padding:calc(var(--spacing)*0);width:50%}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>tbody>tr>td.fi-has-action{padding:calc(var(--spacing)*.5);width:auto}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>tbody>tr>td.fi-has-action .fi-fo-key-value-table-row-sortable-handle{display:flex}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>tbody>tr>td .fi-input{font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-add-action-ctn{display:flex;justify-content:center;padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-fo-key-value-wrp.fi-fo-field-has-inline-label .fi-fo-field-label-col{padding-top:calc(var(--spacing)*1.5)}}.custom-fields-component .fi-fo-markdown-editor{--color-cm-red:#991b1b;--color-cm-orange:#9a3412;--color-cm-amber:#92400e;--color-cm-yellow:#854d0e;--color-cm-lime:#3f6212;--color-cm-green:#166534;--color-cm-emerald:#065f46;--color-cm-teal:#115e59;--color-cm-cyan:#155e75;--color-cm-sky:#075985;--color-cm-blue:#1e40af;--color-cm-indigo:#3730a3;--color-cm-violet:#5b21b6;--color-cm-purple:#6b21a8;--color-cm-fuchsia:#86198f;--color-cm-pink:#9d174d;--color-cm-rose:#9f1239;--color-cm-gray:#18181b;--color-cm-gray-muted:#71717a;--color-cm-gray-background:#e4e4e7}.custom-fields-component .fi-fo-markdown-editor:not(.fi-disabled){color:var(--gray-950);font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));max-width:100%;overflow:hidden}.custom-fields-component .fi-fo-markdown-editor:not(.fi-disabled):where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-markdown-editor.fi-disabled{background-color:var(--gray-50);border-radius:var(--radius-lg);color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*3);width:100%;--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);display:block}.custom-fields-component .fi-fo-markdown-editor.fi-disabled:where(.dark,.dark *){color:var(--gray-400);--tw-ring-color:#ffffff1a;background-color:#0000}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor.fi-disabled:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .CodeMirror{padding-block:calc(var(--spacing)*3)!important;padding-inline:calc(var(--spacing)*4)!important}.custom-fields-component .fi-fo-markdown-editor .cm-s-easymde .cm-comment{background-color:#0000;color:var(--color-cm-gray-muted)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .CodeMirror-cursor{border-color:currentColor}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-keyword{color:var(--color-cm-violet)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-atom{color:var(--color-cm-blue)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-number{color:var(--color-cm-green)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-def{color:var(--color-cm-blue)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-variable{color:var(--color-cm-yellow)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-variable-2{color:var(--color-cm-blue)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-variable-3{color:var(--color-cm-emerald)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-operator,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-property{color:var(--color-cm-gray)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-string,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-string-2{color:var(--color-cm-rose)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-meta{color:var(--color-cm-gray-muted)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-error{color:var(--color-cm-red)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-qualifier{color:var(--color-cm-gray-muted)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-builtin{color:var(--color-cm-violet)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-bracket,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-hr{color:var(--color-cm-gray-muted)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-quote{color:var(--color-cm-sky)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-quote+.cm-quote{color:var(--color-cm-gray-muted)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-list,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-list+.cm-variable-2,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-tab+.cm-variable-2{color:var(--color-cm-gray)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-link{color:var(--color-cm-blue)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-tag{color:var(--color-cm-red)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-attribute{color:var(--color-cm-amber)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-attribute+.cm-string{color:var(--color-cm-green)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-code+.cm-comment:not(.cm-formatting-code){background-color:var(--color-cm-gray-background);color:var(--color-cm-gray)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-1{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-2{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-3{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-4{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-5{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-6{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-comment{background-image:none}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-code-block,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-tab+.cm-comment{background-color:#0000;color:inherit}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .CodeMirror{--tw-border-style:none;background-color:#0000;border-style:none;color:inherit;padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .CodeMirror-scroll{height:auto}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar{border-style:var(--tw-border-style);border-bottom-style:var(--tw-border-style);border-color:var(--gray-200);border-radius:0;border-width:0 0 1px;display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*1);padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*2.5)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button{border-radius:var(--radius-lg);height:calc(var(--spacing)*8);width:calc(var(--spacing)*8);--tw-border-style:none;padding:calc(var(--spacing)*0);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;border-style:none;display:grid;place-content:center;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:focus-visible{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button.active{background-color:var(--gray-50)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button.active:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button.active:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:before{background-color:var(--gray-700);content:"";display:block;height:calc(var(--spacing)*5);-webkit-mask-position:50%;mask-position:50%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;width:calc(var(--spacing)*5)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button.active:before{background-color:var(--primary-600)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .separator{width:calc(var(--spacing)*1);--tw-border-style:none;border-style:none;margin:calc(var(--spacing)*0)!important}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .bold:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M4 3a1 1 0 0 1 1-1h6a4.5 4.5 0 0 1 3.274 7.587A4.75 4.75 0 0 1 11.25 18H5a1 1 0 0 1-1-1zm2.5 5.5v-4H11a2 2 0 1 1 0 4zm0 2.5v4.5h4.75a2.25 2.25 0 0 0 0-4.5z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M4 3a1 1 0 0 1 1-1h6a4.5 4.5 0 0 1 3.274 7.587A4.75 4.75 0 0 1 11.25 18H5a1 1 0 0 1-1-1zm2.5 5.5v-4H11a2 2 0 1 1 0 4zm0 2.5v4.5h4.75a2.25 2.25 0 0 0 0-4.5z' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .italic:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M8 2.75A.75.75 0 0 1 8.75 2h7.5a.75.75 0 0 1 0 1.5h-3.215l-4.483 13h2.698a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3.215l4.483-13H8.75A.75.75 0 0 1 8 2.75' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M8 2.75A.75.75 0 0 1 8.75 2h7.5a.75.75 0 0 1 0 1.5h-3.215l-4.483 13h2.698a.75.75 0 0 1 0 1.5h-7.5a.75.75 0 0 1 0-1.5h3.215l4.483-13H8.75A.75.75 0 0 1 8 2.75' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .strikethrough:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M11.617 3.963c-1.186-.318-2.418-.323-3.416.015-.992.336-1.49.91-1.642 1.476s-.007 1.313.684 2.1c.528.6 1.273 1.1 2.128 1.446h7.879a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5h3.813a6 6 0 0 1-.447-.456C5.18 7.479 4.798 6.231 5.11 5.066c.312-1.164 1.268-2.055 2.61-2.509 1.336-.451 2.877-.42 4.286-.043.856.23 1.684.592 2.409 1.074a.75.75 0 1 1-.83 1.25 6.7 6.7 0 0 0-1.968-.875m1.909 8.123a.75.75 0 0 1 1.015.309c.53.99.607 2.062.18 3.01-.421.94-1.289 1.648-2.441 2.038-1.336.452-2.877.42-4.286.043s-2.759-1.121-3.69-2.18a.75.75 0 1 1 1.127-.99c.696.791 1.765 1.403 2.952 1.721 1.186.318 2.418.323 3.416-.015.853-.288 1.34-.756 1.555-1.232.21-.467.205-1.049-.136-1.69a.75.75 0 0 1 .308-1.014' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M11.617 3.963c-1.186-.318-2.418-.323-3.416.015-.992.336-1.49.91-1.642 1.476s-.007 1.313.684 2.1c.528.6 1.273 1.1 2.128 1.446h7.879a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5h3.813a6 6 0 0 1-.447-.456C5.18 7.479 4.798 6.231 5.11 5.066c.312-1.164 1.268-2.055 2.61-2.509 1.336-.451 2.877-.42 4.286-.043.856.23 1.684.592 2.409 1.074a.75.75 0 1 1-.83 1.25 6.7 6.7 0 0 0-1.968-.875m1.909 8.123a.75.75 0 0 1 1.015.309c.53.99.607 2.062.18 3.01-.421.94-1.289 1.648-2.441 2.038-1.336.452-2.877.42-4.286.043s-2.759-1.121-3.69-2.18a.75.75 0 1 1 1.127-.99c.696.791 1.765 1.403 2.952 1.721 1.186.318 2.418.323 3.416-.015.853-.288 1.34-.756 1.555-1.232.21-.467.205-1.049-.136-1.69a.75.75 0 0 1 .308-1.014' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .link:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath d='M12.232 4.232a2.5 2.5 0 0 1 3.536 3.536l-1.225 1.224a.75.75 0 0 0 1.061 1.06l1.224-1.224a4 4 0 0 0-5.656-5.656l-3 3a4 4 0 0 0 .225 5.865.75.75 0 0 0 .977-1.138 2.5 2.5 0 0 1-.142-3.667z'/%3E%3Cpath d='M11.603 7.963a.75.75 0 0 0-.977 1.138 2.5 2.5 0 0 1 .142 3.667l-3 3a2.5 2.5 0 0 1-3.536-3.536l1.225-1.224a.75.75 0 0 0-1.061-1.06l-1.224 1.224a4 4 0 1 0 5.656 5.656l3-3a4 4 0 0 0-.225-5.865'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath d='M12.232 4.232a2.5 2.5 0 0 1 3.536 3.536l-1.225 1.224a.75.75 0 0 0 1.061 1.06l1.224-1.224a4 4 0 0 0-5.656-5.656l-3 3a4 4 0 0 0 .225 5.865.75.75 0 0 0 .977-1.138 2.5 2.5 0 0 1-.142-3.667z'/%3E%3Cpath d='M11.603 7.963a.75.75 0 0 0-.977 1.138 2.5 2.5 0 0 1 .142 3.667l-3 3a2.5 2.5 0 0 1-3.536-3.536l1.225-1.224a.75.75 0 0 0-1.061-1.06l-1.224 1.224a4 4 0 1 0 5.656 5.656l3-3a4 4 0 0 0-.225-5.865'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .heading:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M2.75 4a.75.75 0 0 1 .75.75v4.5h5v-4.5a.75.75 0 0 1 1.5 0v10.5a.75.75 0 0 1-1.5 0v-4.5h-5v4.5a.75.75 0 0 1-1.5 0V4.75A.75.75 0 0 1 2.75 4M13 8.75a.75.75 0 0 1 .75-.75h1.75a.75.75 0 0 1 .75.75v5.75h1a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1 0-1.5h1v-5h-1a.75.75 0 0 1-.75-.75' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M2.75 4a.75.75 0 0 1 .75.75v4.5h5v-4.5a.75.75 0 0 1 1.5 0v10.5a.75.75 0 0 1-1.5 0v-4.5h-5v4.5a.75.75 0 0 1-1.5 0V4.75A.75.75 0 0 1 2.75 4M13 8.75a.75.75 0 0 1 .75-.75h1.75a.75.75 0 0 1 .75.75v5.75h1a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1 0-1.5h1v-5h-1a.75.75 0 0 1-.75-.75' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .quote:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M10 2c-2.236 0-4.43.18-6.57.524C1.993 2.755 1 4.014 1 5.426v5.148c0 1.413.993 2.67 2.43 2.902q1.753.283 3.55.414c.28.02.521.18.642.413l1.713 3.293a.75.75 0 0 0 1.33 0l1.713-3.293a.78.78 0 0 1 .642-.413 41 41 0 0 0 3.55-.414c1.437-.231 2.43-1.49 2.43-2.902V5.426c0-1.413-.993-2.67-2.43-2.902A41 41 0 0 0 10 2M6.75 6a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5zm0 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M10 2c-2.236 0-4.43.18-6.57.524C1.993 2.755 1 4.014 1 5.426v5.148c0 1.413.993 2.67 2.43 2.902q1.753.283 3.55.414c.28.02.521.18.642.413l1.713 3.293a.75.75 0 0 0 1.33 0l1.713-3.293a.78.78 0 0 1 .642-.413 41 41 0 0 0 3.55-.414c1.437-.231 2.43-1.49 2.43-2.902V5.426c0-1.413-.993-2.67-2.43-2.902A41 41 0 0 0 10 2M6.75 6a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5zm0 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5z' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .code:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M6.28 5.22a.75.75 0 0 1 0 1.06L2.56 10l3.72 3.72a.75.75 0 0 1-1.06 1.06L.97 10.53a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0m7.44 0a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L17.44 10l-3.72-3.72a.75.75 0 0 1 0-1.06m-2.343-3.209a.75.75 0 0 1 .612.867l-2.5 14.5a.75.75 0 0 1-1.478-.255l2.5-14.5a.75.75 0 0 1 .866-.612' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M6.28 5.22a.75.75 0 0 1 0 1.06L2.56 10l3.72 3.72a.75.75 0 0 1-1.06 1.06L.97 10.53a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0m7.44 0a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L17.44 10l-3.72-3.72a.75.75 0 0 1 0-1.06m-2.343-3.209a.75.75 0 0 1 .612.867l-2.5 14.5a.75.75 0 0 1-1.478-.255l2.5-14.5a.75.75 0 0 1 .866-.612' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .unordered-list:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M6 4.75A.75.75 0 0 1 6.75 4h10.5a.75.75 0 0 1 0 1.5H6.75A.75.75 0 0 1 6 4.75M6 10a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H6.75A.75.75 0 0 1 6 10m0 5.25a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H6.75a.75.75 0 0 1-.75-.75M1.99 4.75a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1zm0 10.5a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1zm0-5.25a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M6 4.75A.75.75 0 0 1 6.75 4h10.5a.75.75 0 0 1 0 1.5H6.75A.75.75 0 0 1 6 4.75M6 10a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H6.75A.75.75 0 0 1 6 10m0 5.25a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H6.75a.75.75 0 0 1-.75-.75M1.99 4.75a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1zm0 10.5a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1zm0-5.25a1 1 0 0 1 1-1H3a1 1 0 0 1 1 1v.01a1 1 0 0 1-1 1h-.01a1 1 0 0 1-1-1z' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .ordered-list:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath d='M3 1.25a.75.75 0 0 0 0 1.5h.25v2.5a.75.75 0 0 0 1.5 0V2A.75.75 0 0 0 4 1.25zm-.03 7.404a3.5 3.5 0 0 1 1.524-.12.03.03 0 0 1-.012.012L2.415 9.579A.75.75 0 0 0 2 10.25v1c0 .414.336.75.75.75h2.5a.75.75 0 0 0 0-1.5H3.927l1.225-.613c.52-.26.848-.79.848-1.371 0-.647-.429-1.327-1.193-1.451a5 5 0 0 0-2.277.155.75.75 0 0 0 .44 1.434M7.75 3a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5zm0 6.25a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5zm0 6.25a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5zm-5.125-1.625a.75.75 0 0 0 0 1.5h1.5a.125.125 0 0 1 0 .25H3.5a.75.75 0 0 0 0 1.5h.625a.125.125 0 0 1 0 .25h-1.5a.75.75 0 0 0 0 1.5h1.5a1.625 1.625 0 0 0 1.37-2.5 1.625 1.625 0 0 0-1.37-2.5z'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath d='M3 1.25a.75.75 0 0 0 0 1.5h.25v2.5a.75.75 0 0 0 1.5 0V2A.75.75 0 0 0 4 1.25zm-.03 7.404a3.5 3.5 0 0 1 1.524-.12.03.03 0 0 1-.012.012L2.415 9.579A.75.75 0 0 0 2 10.25v1c0 .414.336.75.75.75h2.5a.75.75 0 0 0 0-1.5H3.927l1.225-.613c.52-.26.848-.79.848-1.371 0-.647-.429-1.327-1.193-1.451a5 5 0 0 0-2.277.155.75.75 0 0 0 .44 1.434M7.75 3a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5zm0 6.25a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5zm0 6.25a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5zm-5.125-1.625a.75.75 0 0 0 0 1.5h1.5a.125.125 0 0 1 0 .25H3.5a.75.75 0 0 0 0 1.5h.625a.125.125 0 0 1 0 .25h-1.5a.75.75 0 0 0 0 1.5h1.5a1.625 1.625 0 0 0 1.37-2.5 1.625 1.625 0 0 0-1.37-2.5z'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .table:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M.99 5.24A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25l.01 9.5A2.25 2.25 0 0 1 16.76 17H3.26A2.267 2.267 0 0 1 1 14.74zm8.26 9.52v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75v.615c0 .414.336.75.75.75h5.373a.75.75 0 0 0 .627-.74m1.5 0a.75.75 0 0 0 .627.74h5.373a.75.75 0 0 0 .75-.75v-.615a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75zm6.75-3.63v-.625a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75v.625c0 .414.336.75.75.75h5.25a.75.75 0 0 0 .75-.75m-8.25 0v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75v.625c0 .414.336.75.75.75H8.5a.75.75 0 0 0 .75-.75M17.5 7.5v-.625a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75V7.5c0 .414.336.75.75.75h5.25a.75.75 0 0 0 .75-.75m-8.25 0v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75V7.5c0 .414.336.75.75.75H8.5a.75.75 0 0 0 .75-.75' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M.99 5.24A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25l.01 9.5A2.25 2.25 0 0 1 16.76 17H3.26A2.267 2.267 0 0 1 1 14.74zm8.26 9.52v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75v.615c0 .414.336.75.75.75h5.373a.75.75 0 0 0 .627-.74m1.5 0a.75.75 0 0 0 .627.74h5.373a.75.75 0 0 0 .75-.75v-.615a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75zm6.75-3.63v-.625a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75v.625c0 .414.336.75.75.75h5.25a.75.75 0 0 0 .75-.75m-8.25 0v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75v.625c0 .414.336.75.75.75H8.5a.75.75 0 0 0 .75-.75M17.5 7.5v-.625a.75.75 0 0 0-.75-.75H11.5a.75.75 0 0 0-.75.75V7.5c0 .414.336.75.75.75h5.25a.75.75 0 0 0 .75-.75m-8.25 0v-.625a.75.75 0 0 0-.75-.75H3.25a.75.75 0 0 0-.75.75V7.5c0 .414.336.75.75.75H8.5a.75.75 0 0 0 .75-.75' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .upload-image:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M1 5.25A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75zm1.5 5.81v3.69c0 .414.336.75.75.75h13.5a.75.75 0 0 0 .75-.75v-2.69l-2.22-2.219a.75.75 0 0 0-1.06 0l-1.91 1.909.47.47a.75.75 0 1 1-1.06 1.06L6.53 8.091a.75.75 0 0 0-1.06 0zM12 7a1 1 0 1 1-2 0 1 1 0 0 1 2 0' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M1 5.25A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75zm1.5 5.81v3.69c0 .414.336.75.75.75h13.5a.75.75 0 0 0 .75-.75v-2.69l-2.22-2.219a.75.75 0 0 0-1.06 0l-1.91 1.909.47.47a.75.75 0 1 1-1.06 1.06L6.53 8.091a.75.75 0 0 0-1.06 0zM12 7a1 1 0 1 1-2 0 1 1 0 0 1 2 0' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .undo:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M7.793 2.232a.75.75 0 0 1-.025 1.06L3.622 7.25h10.003a5.375 5.375 0 0 1 0 10.75H10.75a.75.75 0 0 1 0-1.5h2.875a3.875 3.875 0 0 0 0-7.75H3.622l4.146 3.957a.75.75 0 0 1-1.036 1.085l-5.5-5.25a.75.75 0 0 1 0-1.085l5.5-5.25a.75.75 0 0 1 1.06.025Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M7.793 2.232a.75.75 0 0 1-.025 1.06L3.622 7.25h10.003a5.375 5.375 0 0 1 0 10.75H10.75a.75.75 0 0 1 0-1.5h2.875a3.875 3.875 0 0 0 0-7.75H3.622l4.146 3.957a.75.75 0 0 1-1.036 1.085l-5.5-5.25a.75.75 0 0 1 0-1.085l5.5-5.25a.75.75 0 0 1 1.06.025Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .redo:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M12.207 2.232a.75.75 0 0 0 .025 1.06l4.146 3.958H6.375a5.375 5.375 0 0 0 0 10.75H9.25a.75.75 0 0 0 0-1.5H6.375a3.875 3.875 0 0 1 0-7.75h10.003l-4.146 3.957a.75.75 0 0 0 1.036 1.085l5.5-5.25a.75.75 0 0 0 0-1.085l-5.5-5.25a.75.75 0 0 0-1.06.025Z' clip-rule='evenodd'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' class='size-5' viewBox='0 0 20 20'%3E%3Cpath fill-rule='evenodd' d='M12.207 2.232a.75.75 0 0 0 .025 1.06l4.146 3.958H6.375a5.375 5.375 0 0 0 0 10.75H9.25a.75.75 0 0 0 0-1.5H6.375a3.875 3.875 0 0 1 0-7.75h10.003l-4.146 3.957a.75.75 0 0 0 1.036 1.085l5.5-5.25a.75.75 0 0 0 0-1.085l-5.5-5.25a.75.75 0 0 0-1.06.025Z' clip-rule='evenodd'/%3E%3C/svg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-statusbar{display:none}.custom-fields-component .fi-fo-markdown-editor:where(.dark,.dark *){--color-cm-red:#f87171;--color-cm-orange:#fb923c;--color-cm-amber:#fbbf24;--color-cm-yellow:#facc15;--color-cm-lime:#a3e635;--color-cm-green:#4ade80;--color-cm-emerald:#4ade80;--color-cm-teal:#2dd4bf;--color-cm-cyan:#22d3ee;--color-cm-sky:#38bdf8;--color-cm-blue:#60a5fa;--color-cm-indigo:#818cf8;--color-cm-violet:#a78bfa;--color-cm-purple:#c084fc;--color-cm-fuchsia:#e879f9;--color-cm-pink:#f472b6;--color-cm-rose:#fb7185;--color-cm-gray:#fafafa;--color-cm-gray-muted:#a1a1aa;--color-cm-gray-background:#52525b}.custom-fields-component .fi-fo-markdown-editor:where(.dark,.dark *) .EasyMDEContainer .cm-s-easymde span.CodeMirror-selectedtext{filter:invert()}.custom-fields-component .fi-fo-markdown-editor:where(.dark,.dark *) .EasyMDEContainer .editor-toolbar button:before{background-color:var(--gray-300)}.custom-fields-component .fi-fo-markdown-editor:where(.dark,.dark *) .EasyMDEContainer .editor-toolbar button.active:before{background-color:var(--primary-400)}.custom-fields-component [x-sortable]:has(.fi-sortable-ghost) .fi-fo-markdown-editor{pointer-events:none}.custom-fields-component .fi-fo-modal-table-select:not(.fi-fo-modal-table-select-multiple){align-items:flex-start;column-gap:calc(var(--spacing)*3);--tw-leading:calc(var(--spacing)*5);display:flex;line-height:calc(var(--spacing)*5)}.custom-fields-component .fi-fo-modal-table-select.fi-fo-modal-table-select-multiple{display:grid;gap:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-modal-table-select.fi-fo-modal-table-select-multiple .fi-fo-modal-table-select-badges-ctn{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*1.5)}.custom-fields-component .fi-fo-modal-table-select .fi-fo-modal-table-select-placeholder{color:var(--gray-400)}.custom-fields-component .fi-fo-modal-table-select .fi-fo-modal-table-select-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-fo-radio{gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-radio.fi-inline{display:flex;flex-wrap:wrap}.custom-fields-component .fi-fo-radio:not(.fi-inline).fi-grid-direction-col{margin-top:calc(var(--spacing)*-4)}.custom-fields-component .fi-fo-radio:not(.fi-inline).fi-grid-direction-col>.fi-fo-radio-label{break-inside:avoid;padding-top:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label{align-self:flex-start;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label>.fi-radio-input{flex-shrink:0;margin-top:calc(var(--spacing)*1)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label>.fi-fo-radio-label-text{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);display:grid;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label>.fi-fo-radio-label-text:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label .fi-fo-radio-label-description{--tw-font-weight:var(--font-weight-normal);color:var(--gray-500);font-weight:var(--font-weight-normal)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label .fi-fo-radio-label-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-repeater{display:grid;row-gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-actions{column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-actions.fi-hidden{display:none}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-items{align-items:flex-start;gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item{background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item.fi-collapsed .fi-fo-repeater-item-header-collapsible-actions{rotate:-180deg}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item.fi-collapsed .fi-fo-repeater-item-header-collapse-action,.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item:not(.fi-collapsed) .fi-fo-repeater-item-header-expand-action{opacity:0;pointer-events:none}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;overflow:hidden;padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-repeater.fi-collapsible .fi-fo-repeater-item-header{cursor:pointer;-webkit-user-select:none;user-select:none}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-start-actions{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-icon{color:var(--gray-400)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-label.fi-truncated{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-end-actions{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;margin-inline-start:auto}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-collapsible-actions{position:relative}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-collapse-action,.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-collapsible-actions,.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-expand-action{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-expand-action{inset:calc(var(--spacing)*0);position:absolute;rotate:180deg}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-has-header>.fi-fo-repeater-item-content{border-color:var(--gray-100);border-top-style:var(--tw-border-style);border-top-width:1px}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-has-header>.fi-fo-repeater-item-content:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-has-header>.fi-fo-repeater-item-content:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-content{padding:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add-between-items-ctn{display:flex;justify-content:center;width:100%}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add-between-items{background-color:var(--color-white);border-radius:var(--radius-lg)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add-between-items:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-ctn{align-items:center;display:flex;margin-block:calc(var(--spacing)*-2);position:relative}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-before{border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:1px;flex-shrink:0;width:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-before:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-before:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-inline:calc(var(--spacing)*1);--tw-font-weight:var(--font-weight-medium);color:var(--gray-500);flex-shrink:0;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-after{border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:1px;flex:1}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-after:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-after:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add{display:flex;justify-content:center;width:100%}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add.fi-align-left,.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add.fi-align-end,.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-fo-simple-repeater{display:grid;row-gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-items{gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-item{column-gap:calc(var(--spacing)*3);display:flex;justify-content:flex-start}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-item-content{flex:1}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-item-actions{align-items:center;column-gap:calc(var(--spacing)*1);display:flex}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-add{display:flex;justify-content:center;width:100%}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-add.fi-align-left,.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-add.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-add.fi-align-end,.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-add.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-fo-table-repeater{display:grid;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-table-repeater>table{display:block;width:100%}.custom-fields-component :where(.fi-fo-table-repeater>table>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-fo-table-repeater>table{background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component :where(.fi-fo-table-repeater>table:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-table-repeater>table:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-table-repeater>table:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater>table:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-table-repeater>table>thead{display:none;white-space:nowrap}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th{background-color:var(--gray-50);border-color:var(--gray-200);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:first-of-type{border-top-left-radius:var(--radius-xl)}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:last-of-type{border-top-right-radius:var(--radius-xl)}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:not(:first-of-type){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-align-left,.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-align-start{text-align:start}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-align-end,.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-align-right{text-align:end}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-wrapped{white-space:normal}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:not(.fi-wrapped){white-space:nowrap}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-fo-table-repeater-empty-header-cell{width:calc(var(--spacing)*1)}.custom-fields-component .fi-fo-table-repeater>table>tbody{display:block}.custom-fields-component :where(.fi-fo-table-repeater>table>tbody>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-fo-table-repeater>table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-table-repeater>table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr{display:grid;gap:calc(var(--spacing)*6);padding:calc(var(--spacing)*6)}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td{display:block}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-hidden{display:none}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-vertical-align-start{vertical-align:top}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-vertical-align-center{vertical-align:middle}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-vertical-align-end{vertical-align:bottom}.custom-fields-component .fi-fo-table-repeater>table .fi-fo-table-repeater-header-required-mark{--tw-font-weight:var(--font-weight-medium);color:var(--danger-600);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-table-repeater>table .fi-fo-table-repeater-header-required-mark:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-fo-table-repeater>table .fi-fo-table-repeater-actions{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;height:100%}@supports (container-type:inline-size){.custom-fields-component .fi-fo-table-repeater{container-type:inline-size}@container (min-width:36rem){.custom-fields-component .fi-fo-table-repeater>table{display:table}.custom-fields-component .fi-fo-table-repeater>table>thead{display:table-header-group}.custom-fields-component .fi-fo-table-repeater>table>tbody{display:table-row-group}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr{display:table-row;padding:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td{display:table-cell;padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-hidden{display:table-cell}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-fo-field,.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-in-entry{row-gap:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-fo-field-label-content,.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-in-entry-label{display:none}.custom-fields-component .fi-fo-table-repeater>table .fi-fo-table-repeater-actions{padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td{border-color:var(--gray-200);padding-block:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:not(:first-of-type){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-input-wrp{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor)!important;background-color:#0000!important;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)!important}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-fo-field-wrp-error-message{padding-inline:calc(var(--spacing)*3);padding-bottom:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-fo-radio,.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-in-entry-content{padding-inline:calc(var(--spacing)*3)}}}@supports not (container-type:inline-size){@media (min-width:64rem){.custom-fields-component .fi-fo-table-repeater>table{display:table}.custom-fields-component .fi-fo-table-repeater>table>thead{display:table-header-group}.custom-fields-component .fi-fo-table-repeater>table>tbody{display:table-row-group}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr{display:table-row;padding:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td{display:table-cell;padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-hidden{display:table-cell}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-fo-field,.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-in-entry{row-gap:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-fo-field-label-content,.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-in-entry-label{display:none}.custom-fields-component .fi-fo-table-repeater>table .fi-fo-table-repeater-actions{padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td{border-color:var(--gray-200);padding-block:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:not(:first-of-type){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-input-wrp{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor)!important;background-color:#0000!important;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)!important}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-fo-field-wrp-error-message{padding-inline:calc(var(--spacing)*3);padding-bottom:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-fo-radio,.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-in-entry-content{padding-inline:calc(var(--spacing)*3)}}}.custom-fields-component .fi-fo-table-repeater .fi-fo-table-repeater-add{display:flex;justify-content:center;width:100%}.custom-fields-component .fi-fo-table-repeater .fi-fo-table-repeater-add.fi-align-left,.custom-fields-component .fi-fo-table-repeater .fi-fo-table-repeater-add.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-fo-table-repeater .fi-fo-table-repeater-add.fi-align-end,.custom-fields-component .fi-fo-table-repeater .fi-fo-table-repeater-add.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file{cursor:wait;opacity:.5;pointer-events:none}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-toolbar{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);column-gap:calc(var(--spacing)*3);display:flex;flex-wrap:wrap;padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*2.5);position:relative;row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-toolbar:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-toolbar:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-floating-toolbar{background-color:var(--color-white);border-color:var(--gray-300);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;column-gap:calc(var(--spacing)*3);margin-top:calc(var(--spacing)*-1);max-width:100%;padding:calc(var(--spacing)*1);row-gap:calc(var(--spacing)*1);visibility:hidden;z-index:20;--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);display:flex;flex-wrap:wrap;position:absolute}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-floating-toolbar:where(.dark,.dark *){background-color:var(--gray-800);border-color:var(--gray-600)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-toolbar-group{column-gap:calc(var(--spacing)*1);display:flex}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool{border-radius:var(--radius-lg);font-size:var(--text-sm);height:calc(var(--spacing)*8);line-height:var(--tw-leading,var(--text-sm--line-height));min-width:calc(var(--spacing)*8);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-700);font-weight:var(--font-weight-semibold);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;align-items:center;display:flex;justify-content:center;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:focus-visible{background-color:var(--gray-50)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:where(.dark,.dark *){color:var(--gray-200)}@media (hover:hover){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool[disabled]{cursor:default;opacity:.7;pointer-events:none}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool.fi-active{background-color:var(--gray-50);color:var(--primary-600)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool.fi-active:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool.fi-active:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool.fi-active:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool.fi-fo-rich-editor-tool-with-label{align-items:center;column-gap:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*1.5)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message{align-items:center;background-color:var(--gray-50);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);column-gap:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*5);--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);display:flex;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message .fi-loading-indicator{color:var(--gray-400)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message .fi-loading-indicator:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message{align-items:center;background-color:var(--danger-50);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--danger-200);column-gap:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*5);--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--danger-700);display:flex;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message:where(.dark,.dark *){color:var(--danger-200)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-main{display:flex;flex-direction:column-reverse}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-content{flex:1;min-height:calc(var(--spacing)*12);padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*5);position:relative;width:100%}.custom-fields-component .fi-fo-rich-editor span[data-type=mergeTag]{display:inline-block;margin-block:calc(var(--spacing)*0);white-space:nowrap}.custom-fields-component .fi-fo-rich-editor span[data-type=mergeTag]:before{--tw-font-weight:var(--font-weight-normal);content:"{{";font-weight:var(--font-weight-normal);margin-inline-end:calc(var(--spacing)*1);opacity:.6}.custom-fields-component .fi-fo-rich-editor span[data-type=mergeTag]:after{--tw-font-weight:var(--font-weight-normal);content:"}}";font-weight:var(--font-weight-normal);margin-inline-start:calc(var(--spacing)*1);opacity:.6}.custom-fields-component .fi-fo-rich-editor span[data-type=mention]{background-color:var(--primary-50);margin-block:calc(var(--spacing)*0);padding-inline:calc(var(--spacing)*1);--tw-font-weight:var(--font-weight-medium);border-radius:.25rem;color:var(--primary-600);display:inline-block;font-weight:var(--font-weight-medium);white-space:nowrap}.custom-fields-component .fi-fo-rich-editor span[data-type=mention]:where(.dark,.dark *){background-color:var(--primary-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor span[data-type=mention]:where(.dark,.dark *){background-color:color-mix(in oklab,var(--primary-400)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor span[data-type=mention]:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels{background-color:var(--gray-50);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);width:100%}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900)30%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panel-header{align-items:flex-start;display:flex;gap:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panel-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);flex:1;font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panel-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panel-close-btn-ctn{flex-shrink:0}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panel{display:grid}.custom-fields-component :where(.fi-fo-rich-editor .fi-fo-rich-editor-panel>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-fo-rich-editor .fi-fo-rich-editor-panel:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-rich-editor .fi-fo-rich-editor-panel:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tags-list{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn{background-color:var(--color-white);border-radius:var(--radius-lg);color:var(--gray-600);cursor:move;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding:calc(var(--spacing)*1);text-align:start;--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn{--tw-ring-color:color-mix(in oklab,var(--gray-600)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn:where(.dark,.dark *){background-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-400)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn:where(.dark,.dark *){color:var(--gray-200);--tw-ring-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--gray-400)20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-blocks-list{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn{background-color:var(--color-white);border-radius:var(--radius-lg);color:var(--gray-600);cursor:move;font-size:var(--text-sm);gap:calc(var(--spacing)*1.5);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*1);padding-inline:calc(var(--spacing)*2);text-align:start;--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-600);display:flex}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn{--tw-ring-color:color-mix(in oklab,var(--gray-600)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn:where(.dark,.dark *){background-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-400)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn:where(.dark,.dark *){color:var(--gray-200);--tw-ring-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--gray-400)20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap{height:100%}.custom-fields-component .fi-fo-rich-editor .tiptap:focus{--tw-outline-style:none;outline-style:none}.custom-fields-component div:is(.fi-fo-rich-editor .tiptap:focus .ProseMirror-selectednode)[data-type=customBlock],.custom-fields-component img:is(.fi-fo-rich-editor .tiptap:focus .ProseMirror-selectednode){--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component :is(div:is(.fi-fo-rich-editor .tiptap:focus .ProseMirror-selectednode)[data-type=customBlock],img:is(.fi-fo-rich-editor .tiptap:focus .ProseMirror-selectednode)):where(.dark,.dark *){--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-fo-rich-editor .tiptap p.is-editor-empty:first-child:before{color:var(--gray-400);content:attr(data-placeholder);float:inline-start;height:calc(var(--spacing)*0);pointer-events:none}.custom-fields-component .fi-fo-rich-editor .tiptap p.is-editor-empty:first-child:where(.dark,.dark *):before{color:var(--gray-500)}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]{border-color:var(--gray-950);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;display:flex;gap:calc(var(--spacing)*1);margin-block:calc(var(--spacing)*6)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]{border-color:color-mix(in oklab,var(--gray-950)20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]{padding:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]:where(.dark,.dark *){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>div:first-of-type{margin-top:calc(var(--spacing)*0)!important}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details] summary{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);list-style-type:none}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button{border-radius:var(--radius-md);font-size:var(--text-xs);height:calc(var(--spacing)*5);line-height:var(--tw-leading,var(--text-xs--line-height));margin-right:calc(var(--spacing)*2);margin-top:1px;padding:calc(var(--spacing)*1);width:calc(var(--spacing)*5);--tw-leading:1;align-items:center;background-color:#0000;display:flex;justify-content:center;line-height:1}@media (hover:hover){.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button:hover{background-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button:hover{background-color:color-mix(in oklab,var(--gray-950)5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button:before{content:"ā–¶"}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details].is-open>button:before{transform:rotate(90deg)}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>div{display:flex;flex-direction:column;gap:calc(var(--spacing)*4);width:100%}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>div>[data-type=detailsContent]{margin-top:calc(var(--spacing)*0)!important}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>div>[data-type=detailsContent]>:last-child{margin-bottom:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-rich-editor .tiptap table{border-collapse:collapse;margin:calc(var(--spacing)*0);overflow:hidden;table-layout:fixed;width:100%}.custom-fields-component .fi-fo-rich-editor .tiptap table:first-child{margin-top:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-rich-editor .tiptap table td,.custom-fields-component .fi-fo-rich-editor .tiptap table th{border-color:var(--gray-300);border-style:var(--tw-border-style);border-width:1px;min-width:1em;padding:calc(var(--spacing)*2)!important;position:relative;vertical-align:top}.custom-fields-component :is(.fi-fo-rich-editor .tiptap table td,.fi-fo-rich-editor .tiptap table th):where(.dark,.dark *){border-color:var(--gray-600)}.custom-fields-component :is(.fi-fo-rich-editor .tiptap table td,.fi-fo-rich-editor .tiptap table th)>*{margin-bottom:calc(var(--spacing)*0)}.custom-fields-component .fi-fo-rich-editor .tiptap table th{background-color:var(--gray-100);text-align:start;--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.custom-fields-component .fi-fo-rich-editor .tiptap table th:where(.dark,.dark *){background-color:var(--gray-800);color:var(--color-white)}.custom-fields-component .fi-fo-rich-editor .tiptap table .selectedCell:after{background-color:var(--gray-200);bottom:calc(var(--spacing)*0);inset-inline-end:calc(var(--spacing)*0);inset-inline-start:calc(var(--spacing)*0);pointer-events:none;position:absolute;top:calc(var(--spacing)*0);z-index:2}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap table .selectedCell:after{background-color:color-mix(in oklab,var(--gray-200)80%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap table .selectedCell:after{--tw-content:"";content:var(--tw-content)}.custom-fields-component .fi-fo-rich-editor .tiptap table .selectedCell:where(.dark,.dark *):after{background-color:var(--gray-800)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap table .selectedCell:where(.dark,.dark *):after{background-color:color-mix(in oklab,var(--gray-800)80%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap table .column-resize-handle{background-color:var(--primary-600);bottom:calc(var(--spacing)*0);inset-inline-end:calc(var(--spacing)*0);margin:calc(var(--spacing)*0)!important;pointer-events:none;position:absolute;top:calc(var(--spacing)*0);width:calc(var(--spacing)*1)}.custom-fields-component .fi-fo-rich-editor .tiptap .tableWrapper{overflow-x:auto}.custom-fields-component .fi-fo-rich-editor .tiptap.resize-cursor{cursor:col-resize;cursor:ew-resize}.custom-fields-component .fi-fo-rich-editor .tiptap .grid-layout>.grid-layout-col{border-color:var(--gray-950);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap .grid-layout>.grid-layout-col{border-color:color-mix(in oklab,var(--gray-950)20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap .grid-layout>.grid-layout-col{padding:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-rich-editor .tiptap .grid-layout>.grid-layout-col:where(.dark,.dark *){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap .grid-layout>.grid-layout-col:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle]{background:#00000080;border:1px solid #fffc;border-radius:2px;position:absolute;z-index:10}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle]:hover{background:#000c}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle]{margin:0!important}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom-left],.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom-right],.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top-left],.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top-right]{height:8px;width:8px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top-left]{cursor:nwse-resize;left:-4px;top:-4px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top-right]{cursor:nesw-resize;right:-4px;top:-4px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom-left]{bottom:-4px;cursor:nesw-resize;left:-4px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom-right]{bottom:-4px;cursor:nwse-resize;right:-4px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom],.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top]{height:6px;left:8px;right:8px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top]{cursor:ns-resize;top:-3px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom]{bottom:-3px;cursor:ns-resize}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=left],.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=right]{bottom:8px;top:8px;width:6px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=left]{cursor:ew-resize;left:-3px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=right]{cursor:ew-resize;right:-3px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-state=true] [data-resize-wrapper]{border-radius:.125rem;outline:1px solid #00000040;position:relative}@supports (-webkit-touch-callout:none){.custom-fields-component .fi-fo-rich-editor .tiptap.ProseMirror{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.custom-fields-component .fi-fo-rich-editor img{display:inline-block}.custom-fields-component .fi-fo-rich-editor div[data-type=customBlock]{display:grid}.custom-fields-component :where(.fi-fo-rich-editor div[data-type=customBlock]>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-fo-rich-editor div[data-type=customBlock]{border-radius:var(--radius-lg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);overflow:hidden}.custom-fields-component :where(.fi-fo-rich-editor div[data-type=customBlock]:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-rich-editor div[data-type=customBlock]:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-rich-editor div[data-type=customBlock]:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor div[data-type=customBlock]:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-header{align-items:flex-start;background-color:var(--gray-50);display:flex;gap:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-header:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-header:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900)30%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);flex:1;font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-delete-btn-ctn,.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-edit-btn-ctn{flex-shrink:0}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-preview{padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4)}@supports (container-type:inline-size){.custom-fields-component .fi-fo-rich-editor{container-type:inline-size}@container (min-width:42rem){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-main{flex-direction:row}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels{border-bottom-style:var(--tw-border-style);border-bottom-width:0;border-end-end-radius:var(--radius-lg);border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px;max-width:var(--container-3xs)}}}@supports not (container-type:inline-size){@media (min-width:48rem){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-main{flex-direction:row}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels{border-bottom-style:var(--tw-border-style);border-bottom-width:0;border-end-end-radius:var(--radius-lg);border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px;max-width:var(--container-3xs)}}}.custom-fields-component :scope .fi-fo-rich-editor-text-color-select-option{align-items:center;display:flex;gap:calc(var(--spacing)*2)}.custom-fields-component :scope .fi-fo-rich-editor-text-color-select-option .fi-fo-rich-editor-text-color-select-option-preview{background-color:var(--color);border-radius:3.40282e+38px;flex-shrink:0;height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}.custom-fields-component :scope .fi-fo-rich-editor-text-color-select-option .fi-fo-rich-editor-text-color-select-option-preview:where(.dark,.dark *){background-color:var(--dark-color)}.custom-fields-component [x-sortable]:has(.fi-sortable-ghost) .fi-fo-rich-editor{pointer-events:none}.custom-fields-component .fi-fo-select .fi-hidden{display:none}@media (min-width:40rem){.custom-fields-component .fi-fo-select-wrp.fi-fo-field-has-inline-label .fi-fo-field-label-col{padding-top:calc(var(--spacing)*1.5)}}.custom-fields-component .fi-fo-slider{border-radius:var(--radius-lg);border-style:var(--tw-border-style);gap:calc(var(--spacing)*4);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);background-color:#0000;border-width:0}.custom-fields-component .fi-fo-slider:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-fo-slider .noUi-connect{background-color:var(--primary-500)}.custom-fields-component .fi-fo-slider .noUi-connect:where(.dark,.dark *){background-color:var(--primary-600)}.custom-fields-component .fi-fo-slider .noUi-connects{background-color:var(--gray-950);border-radius:var(--radius-lg)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-connects{background-color:color-mix(in oklab,var(--gray-950)5%,transparent)}}.custom-fields-component .fi-fo-slider .noUi-connects:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-connects:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-fo-slider .noUi-handle{border-color:var(--gray-950);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;position:absolute}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-handle{border-color:color-mix(in oklab,var(--gray-950)10%,transparent)}}.custom-fields-component .fi-fo-slider .noUi-handle{background-color:var(--color-white);--tw-shadow:0 0 #0000;backface-visibility:hidden;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-fo-slider .noUi-handle:focus{outline-color:var(--primary-600);outline-style:var(--tw-outline-style);outline-width:2px}.custom-fields-component .fi-fo-slider .noUi-handle:where(.dark,.dark *){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-handle:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-fo-slider .noUi-handle:where(.dark,.dark *){background-color:var(--gray-700)}.custom-fields-component .fi-fo-slider .noUi-handle:where(.dark,.dark *):focus{outline-color:var(--primary-500)}.custom-fields-component .fi-fo-slider .noUi-handle:after,.custom-fields-component .fi-fo-slider .noUi-handle:before{background-color:var(--gray-400);border-style:var(--tw-border-style);border-width:0}.custom-fields-component .fi-fo-slider .noUi-tooltip{background-color:var(--color-white);border-radius:var(--radius-md);border-style:var(--tw-border-style);color:var(--gray-950);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);border-width:0}.custom-fields-component .fi-fo-slider .noUi-tooltip:where(.dark,.dark *){background-color:var(--gray-800);color:var(--color-white);--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-tooltip:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-fo-slider .noUi-pips .noUi-value{color:var(--gray-950)}.custom-fields-component .fi-fo-slider .noUi-pips .noUi-value:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-slider.fi-fo-slider-vertical{height:calc(var(--spacing)*40);margin-top:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-slider.fi-fo-slider-vertical.fi-fo-slider-has-tooltips{margin-inline-start:calc(var(--spacing)*10)}.custom-fields-component .fi-fo-slider:not(.fi-fo-slider-vertical).fi-fo-slider-has-pips{margin-bottom:calc(var(--spacing)*8)}.custom-fields-component .fi-fo-slider:not(.fi-fo-slider-vertical).fi-fo-slider-has-tooltips{margin-top:calc(var(--spacing)*10)}.custom-fields-component .fi-fo-slider:not(.fi-fo-slider-vertical) .noUi-pips .noUi-value{margin-top:calc(var(--spacing)*1)}.custom-fields-component .fi-fo-tags-input.fi-disabled .fi-badge-delete-btn{display:none}.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn{border-top:1px var(--tw-border-style) var(--gray-200);display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*1.5);padding:calc(var(--spacing)*2);width:100%}.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn:where(.dark,.dark *){border-top-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn:where(.dark,.dark *){border-top-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn>template{display:none}.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn>.fi-badge.fi-reorderable{cursor:move}.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn>.fi-badge .fi-badge-label-ctn{text-align:start;-webkit-user-select:none;user-select:none}@media (min-width:40rem){.custom-fields-component .fi-fo-tags-input-wrp.fi-fo-field-has-inline-label .fi-fo-field-label-col{padding-top:calc(var(--spacing)*1.5)}}.custom-fields-component .fi-fo-text-input{overflow:hidden}.custom-fields-component .fi-fo-text-input input.fi-revealable::-ms-reveal{display:none}.custom-fields-component .fi-fo-textarea{overflow:hidden}.custom-fields-component .fi-fo-textarea textarea{--tw-border-style:none;font-size:var(--text-sm);height:100%;line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*3);width:100%;--tw-leading:calc(var(--spacing)*6);background-color:#0000;border-style:none;color:var(--gray-950);display:block;line-height:calc(var(--spacing)*6)}.custom-fields-component .fi-fo-textarea textarea::placeholder{color:var(--gray-400)}.custom-fields-component .fi-fo-textarea textarea:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-fo-textarea textarea:disabled{color:var(--gray-500);-webkit-text-fill-color:var(--color-gray-500)}.custom-fields-component .fi-fo-textarea textarea:disabled::placeholder{-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component .fi-fo-textarea textarea:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-textarea textarea:where(.dark,.dark *)::placeholder{color:var(--gray-500)}.custom-fields-component .fi-fo-textarea textarea:where(.dark,.dark *):disabled{color:var(--gray-400);-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component .fi-fo-textarea textarea:where(.dark,.dark *):disabled::placeholder{-webkit-text-fill-color:var(--color-gray-500)}@supports (-webkit-touch-callout:none){.custom-fields-component .fi-fo-textarea textarea{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.custom-fields-component .fi-fo-textarea.fi-autosizable textarea{resize:none}@media (min-width:40rem){.custom-fields-component .fi-fo-textarea-wrp.fi-fo-field-has-inline-label .fi-fo-field-label-col{padding-top:calc(var(--spacing)*1.5)}}.custom-fields-component .fi-fo-toggle-buttons.fi-btn-group{width:max-content}.custom-fields-component .fi-fo-toggle-buttons:not(.fi-btn-group){gap:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-toggle-buttons:not(.fi-btn-group).fi-inline{display:flex;flex-wrap:wrap}.custom-fields-component .fi-fo-toggle-buttons:not(.fi-btn-group):not(.fi-inline).fi-grid-direction-col{margin-top:calc(var(--spacing)*-3)}.custom-fields-component .fi-fo-toggle-buttons:not(.fi-btn-group):not(.fi-inline).fi-grid-direction-col .fi-fo-toggle-buttons-btn-ctn{break-inside:avoid;padding-top:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-toggle-buttons .fi-fo-toggle-buttons-input{opacity:0;pointer-events:none;position:absolute}@media (min-width:40rem){.custom-fields-component .fi-fo-toggle-buttons-wrp.fi-fo-field-has-inline-label .fi-fo-field-label-col{padding-top:calc(var(--spacing)*1.5)}}.custom-fields-component .fi-in-code .phiki{border-radius:var(--radius-lg);padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent);overflow-x:auto}.custom-fields-component .fi-in-code .phiki:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-code .phiki:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-in-code:where(.dark,.dark *) .phiki,.custom-fields-component .fi-in-code:where(.dark,.dark *) .phiki span{background-color:var(--phiki-dark-background-color)!important;color:var(--phiki-dark-color)!important;font-style:var(--phiki-dark-font-style)!important;font-weight:var(--phiki-dark-font-weight)!important;-webkit-text-decoration:var(--phiki-dark-text-decoration)!important;text-decoration:var(--phiki-dark-text-decoration)!important}.custom-fields-component .fi-in-code.fi-copyable{cursor:pointer}.custom-fields-component .fi-in-color{display:flex;gap:calc(var(--spacing)*1.5);width:100%}.custom-fields-component .fi-in-color.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-in-color.fi-align-left,.custom-fields-component .fi-in-color.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-in-color.fi-align-center{justify-content:center}.custom-fields-component .fi-in-color.fi-align-end,.custom-fields-component .fi-in-color.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-in-color.fi-align-between,.custom-fields-component .fi-in-color.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-in-color>.fi-in-color-item{border-radius:var(--radius-md);height:calc(var(--spacing)*6);width:calc(var(--spacing)*6)}.custom-fields-component .fi-in-color>.fi-in-color-item.fi-copyable{cursor:pointer}.custom-fields-component .fi-in-entry{display:grid;row-gap:calc(var(--spacing)*2)}@media (min-width:40rem){.custom-fields-component .fi-in-entry.fi-in-entry-has-inline-label{align-items:flex-start;column-gap:calc(var(--spacing)*4);grid-template-columns:repeat(3,minmax(0,1fr))}.custom-fields-component .fi-in-entry.fi-in-entry-has-inline-label .fi-in-entry-content-col{grid-column:span 2/span 2}}.custom-fields-component .fi-in-entry .fi-in-entry-label-ctn{align-items:flex-start;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-in-entry .fi-in-entry-label-ctn>.fi-sc:first-child{flex-grow:0}.custom-fields-component .fi-in-entry .fi-in-entry-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-in-entry .fi-in-entry-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-in-entry .fi-in-entry-label.fi-hidden{display:none}.custom-fields-component .fi-in-entry .fi-in-entry-content-col,.custom-fields-component .fi-in-entry .fi-in-entry-label-col{display:grid;grid-auto-columns:minmax(0,1fr);row-gap:calc(var(--spacing)*2)}.custom-fields-component .fi-in-entry .fi-in-entry-content-ctn{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;width:100%}.custom-fields-component .fi-in-entry .fi-in-entry-content{display:block;text-align:start;width:100%}.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-center{text-align:center}.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-end{text-align:end}.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-left{text-align:left}.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-right{text-align:right}.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-between,.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-justify{text-align:justify}.custom-fields-component .fi-in-entry .fi-in-placeholder{color:var(--gray-400);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-in-entry .fi-in-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-in-key-value{table-layout:auto;width:100%}.custom-fields-component :where(.fi-in-key-value>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-in-key-value{background-color:var(--color-white);border-radius:var(--radius-lg);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component :where(.fi-in-key-value:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-in-key-value:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-in-key-value:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-key-value:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-in-key-value:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-key-value:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-in-key-value th{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3);text-align:start;--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-in-key-value th:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component :where(.fi-in-key-value tbody>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-in-key-value tbody{font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}@media (min-width:40rem){.custom-fields-component .fi-in-key-value tbody{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}}.custom-fields-component :where(.fi-in-key-value tbody:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-in-key-value tbody:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component :where(.fi-in-key-value tr>:not(:last-child)){--tw-divide-x-reverse:0;border-color:var(--gray-200);border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style)}.custom-fields-component :where(.fi-in-key-value tr:where(:dir(rtl),[dir=rtl],[dir=rtl] *)>:not(:last-child)){--tw-divide-x-reverse:1}.custom-fields-component :where(.fi-in-key-value tr:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-in-key-value tr:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-in-key-value td{overflow-wrap:anywhere;padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*3);width:50%}.custom-fields-component .fi-in-key-value td.fi-in-placeholder{font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";padding-block:calc(var(--spacing)*2);text-align:center;width:100%}.custom-fields-component .fi-in-icon{display:flex;gap:calc(var(--spacing)*1.5);width:100%}.custom-fields-component .fi-in-icon.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-in-icon.fi-in-icon-has-line-breaks{flex-direction:column}.custom-fields-component .fi-in-icon.fi-align-left,.custom-fields-component .fi-in-icon.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-in-icon.fi-align-center{justify-content:center}.custom-fields-component .fi-in-icon.fi-align-end,.custom-fields-component .fi-in-icon.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-in-icon.fi-align-between,.custom-fields-component .fi-in-icon.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-in-icon>.fi-icon,.custom-fields-component .fi-in-icon>a>.fi-icon{color:var(--gray-400)}.custom-fields-component :is(.fi-in-icon>.fi-icon,.fi-in-icon>a>.fi-icon):where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component :is(.fi-in-icon>.fi-icon,.fi-in-icon>a>.fi-icon).fi-color{color:var(--text)}.custom-fields-component :is(.fi-in-icon>.fi-icon,.fi-in-icon>a>.fi-icon).fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-in-image{align-items:center;display:flex;gap:calc(var(--spacing)*1.5);width:100%}.custom-fields-component .fi-in-image img{max-width:none;object-fit:cover;object-position:center}.custom-fields-component .fi-in-image.fi-circular img{border-radius:3.40282e+38px}.custom-fields-component .fi-in-image.fi-in-image-ring .fi-in-image-limited-remaining-text,.custom-fields-component .fi-in-image.fi-in-image-ring img{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-white)}.custom-fields-component :is(.fi-in-image.fi-in-image-ring img,.fi-in-image.fi-in-image-ring .fi-in-image-limited-remaining-text):where(.dark,.dark *){--tw-ring-color:var(--gray-900)}.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-1 .fi-in-image-limited-remaining-text,.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-1 img{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-2 .fi-in-image-limited-remaining-text,.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-2 img{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-4 .fi-in-image-limited-remaining-text,.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-4 img{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(4px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-in-image.fi-in-image-overlap-1{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-1*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-1*var(--tw-space-x-reverse))}.custom-fields-component .fi-in-image.fi-in-image-overlap-2{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-2*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-2*var(--tw-space-x-reverse))}.custom-fields-component .fi-in-image.fi-in-image-overlap-3{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-3*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-3*var(--tw-space-x-reverse))}.custom-fields-component .fi-in-image.fi-in-image-overlap-4{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-4*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-4*var(--tw-space-x-reverse))}.custom-fields-component .fi-in-image.fi-in-image-overlap-5{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-5*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-5*var(--tw-space-x-reverse))}.custom-fields-component .fi-in-image.fi-in-image-overlap-6{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-6*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-6*var(--tw-space-x-reverse))}.custom-fields-component .fi-in-image.fi-in-image-overlap-7{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-7>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-7*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-7*var(--tw-space-x-reverse))}.custom-fields-component .fi-in-image.fi-in-image-overlap-8{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-8*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-8*var(--tw-space-x-reverse))}.custom-fields-component .fi-in-image.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-in-image.fi-align-left,.custom-fields-component .fi-in-image.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-in-image.fi-align-center{justify-content:center}.custom-fields-component .fi-in-image.fi-align-end,.custom-fields-component .fi-in-image.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-in-image.fi-align-between,.custom-fields-component .fi-in-image.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-in-image.fi-stacked .fi-in-image-limited-remaining-text{background-color:var(--gray-100);border-radius:3.40282e+38px}.custom-fields-component .fi-in-image.fi-stacked .fi-in-image-limited-remaining-text:where(.dark,.dark *){background-color:var(--gray-800)}.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);align-items:center;color:var(--gray-500);display:flex;font-weight:var(--font-weight-medium);justify-content:center}.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text.fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text.fi-size-base,.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text.fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text.fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component ul.fi-in-repeatable{gap:calc(var(--spacing)*4)}.custom-fields-component .fi-in-repeatable .fi-in-repeatable-item{display:block}.custom-fields-component .fi-in-repeatable.fi-contained .fi-in-repeatable-item{background-color:var(--color-white);border-radius:var(--radius-xl);padding:calc(var(--spacing)*4);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component .fi-in-repeatable.fi-contained .fi-in-repeatable-item:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-repeatable.fi-contained .fi-in-repeatable-item:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-in-repeatable.fi-contained .fi-in-repeatable-item:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-repeatable.fi-contained .fi-in-repeatable-item:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-in-table-repeatable{display:grid;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-in-table-repeatable>table{display:block;width:100%}.custom-fields-component :where(.fi-in-table-repeatable>table>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-in-table-repeatable>table{background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component :where(.fi-in-table-repeatable>table:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-in-table-repeatable>table:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-in-table-repeatable>table:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-table-repeatable>table:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-in-table-repeatable>table>thead{display:none;white-space:nowrap}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th{background-color:var(--gray-50);border-color:var(--gray-200);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3);text-align:start;--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:first-of-type{border-top-left-radius:var(--radius-xl)}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:last-of-type{border-top-right-radius:var(--radius-xl)}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:not(:first-of-type){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th.fi-align-center{text-align:center}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th.fi-align-end,.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th.fi-align-right{text-align:end}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th.fi-wrapped{white-space:normal}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:not(.fi-wrapped){white-space:nowrap}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th.fi-in-table-repeatable-empty-header-cell{width:calc(var(--spacing)*1)}.custom-fields-component .fi-in-table-repeatable>table>tbody{display:block}.custom-fields-component :where(.fi-in-table-repeatable>table>tbody>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-in-table-repeatable>table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-in-table-repeatable>table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr{display:grid;gap:calc(var(--spacing)*6);padding:calc(var(--spacing)*6)}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td{display:block}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td.fi-hidden{display:none}@supports (container-type:inline-size){.custom-fields-component .fi-in-table-repeatable{container-type:inline-size}@container (min-width:36rem){.custom-fields-component .fi-in-table-repeatable>table{display:table}.custom-fields-component .fi-in-table-repeatable>table>thead{display:table-header-group}.custom-fields-component .fi-in-table-repeatable>table>tbody{display:table-row-group}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr{display:table-row;padding:calc(var(--spacing)*0)}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td{display:table-cell;padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td.fi-hidden{display:table-cell}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td .fi-in-entry{row-gap:calc(var(--spacing)*0)}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td .fi-in-entry-label{display:none}.custom-fields-component .fi-in-table-repeatable>table .fi-in-table-repeatable-actions{padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}}}@supports not (container-type:inline-size){@media (min-width:64rem){.custom-fields-component .fi-in-table-repeatable>table{display:table}.custom-fields-component .fi-in-table-repeatable>table>thead{display:table-header-group}.custom-fields-component .fi-in-table-repeatable>table>tbody{display:table-row-group}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr{display:table-row;padding:calc(var(--spacing)*0)}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td{display:table-cell;padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td.fi-hidden{display:table-cell}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td .fi-in-entry{row-gap:calc(var(--spacing)*0)}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td .fi-in-entry-label{display:none}}}.custom-fields-component .fi-in-text{width:100%}.custom-fields-component .fi-in-text.fi-in-text-affixed{display:flex;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-in-text .fi-in-text-affixed-content{flex:1;min-width:calc(var(--spacing)*0)}.custom-fields-component .fi-in-text .fi-in-text-affix{align-items:center;align-self:stretch;display:flex;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-in-text.fi-in-text-list-limited{display:flex;flex-direction:column}.custom-fields-component .fi-in-text.fi-in-text-list-limited.fi-in-text-has-badges{row-gap:calc(var(--spacing)*2)}.custom-fields-component .fi-in-text.fi-in-text-list-limited:not(.fi-in-text-has-badges){row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-in-text.fi-bulleted ul,.custom-fields-component ul.fi-in-text.fi-bulleted{list-style-position:inside;list-style-type:disc}.custom-fields-component .fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges ul,.custom-fields-component ul.fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges{column-gap:calc(var(--spacing)*1.5);display:flex}.custom-fields-component :is(ul.fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges,.fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges ul).fi-wrapped,.custom-fields-component :is(ul.fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges,.fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges ul):is(.fi-wrapped ul){flex-wrap:wrap;row-gap:calc(var(--spacing)*1)}.custom-fields-component :is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul).fi-in-text-has-line-breaks,.custom-fields-component :is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):is(.fi-in-text-has-line-breaks ul){display:flex;flex-direction:column;row-gap:calc(var(--spacing)*1)}.custom-fields-component :is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(.fi-in-text-has-line-breaks ul),.custom-fields-component :is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(ul.fi-in-text-has-line-breaks){column-gap:calc(var(--spacing)*1.5);display:flex}.custom-fields-component :is(:is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(ul.fi-in-text-has-line-breaks),:is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(.fi-in-text-has-line-breaks ul)).fi-wrapped,.custom-fields-component :is(:is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(ul.fi-in-text-has-line-breaks),:is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(.fi-in-text-has-line-breaks ul)):is(.fi-wrapped ul){flex-wrap:wrap;row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-in-text.fi-wrapped:not(.fi-in-text-has-badges.fi-in-text-has-line-breaks){overflow-wrap:break-word;white-space:normal}.custom-fields-component .fi-in-text.fi-wrapped:not(.fi-in-text-has-badges.fi-in-text-has-line-breaks) .fi-badge,.custom-fields-component .fi-in-text.fi-wrapped:not(.fi-in-text-has-badges.fi-in-text-has-line-breaks) .fi-in-text-list-limited-message{white-space:nowrap}.custom-fields-component .fi-in-text>.fi-in-text-list-limited-message{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-in-text>.fi-in-text-list-limited-message:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-in-text.fi-align-center{text-align:center}.custom-fields-component .fi-in-text.fi-align-center ul,.custom-fields-component ul.fi-in-text.fi-align-center{justify-content:center}.custom-fields-component .fi-in-text.fi-align-end,.custom-fields-component .fi-in-text.fi-align-right{text-align:end}.custom-fields-component :is(.fi-in-text.fi-align-end,.fi-in-text.fi-align-right) ul,.custom-fields-component ul:is(.fi-in-text.fi-align-end,.fi-in-text.fi-align-right){justify-content:flex-end}.custom-fields-component .fi-in-text.fi-align-between,.custom-fields-component .fi-in-text.fi-align-justify{text-align:justify}.custom-fields-component :is(.fi-in-text.fi-align-justify,.fi-in-text.fi-align-between) ul,.custom-fields-component ul:is(.fi-in-text.fi-align-justify,.fi-in-text.fi-align-between){justify-content:space-between}.custom-fields-component .fi-in-text-item{color:var(--gray-950)}.custom-fields-component .fi-in-text-item:where(.dark,.dark *){color:var(--color-white)}@media (hover:hover){.custom-fields-component .fi-in-text-item a:hover{text-decoration-line:underline}}.custom-fields-component .fi-in-text-item a:focus-visible{text-decoration-line:underline}.custom-fields-component .fi-in-text-item:not(.fi-bulleted li.fi-in-text-item){-webkit-line-clamp:var(--line-clamp,none);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.custom-fields-component .fi-in-text-item>.fi-copyable{cursor:pointer}.custom-fields-component .fi-in-text-item.fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-in-text-item.fi-size-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-in-text-item.fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-in-text-item.fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-in-text-item.fi-font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.custom-fields-component .fi-in-text-item.fi-font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.custom-fields-component .fi-in-text-item.fi-font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.custom-fields-component .fi-in-text-item.fi-font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.custom-fields-component .fi-in-text-item.fi-font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-in-text-item.fi-font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-in-text-item.fi-font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.custom-fields-component .fi-in-text-item.fi-font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.custom-fields-component .fi-in-text-item.fi-font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.custom-fields-component .fi-in-text-item.fi-font-sans{font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.custom-fields-component .fi-in-text-item.fi-font-serif{font-family:var(--serif-font-family),ui-serif,Georgia,Cambria,"Times New Roman",Times,serif}.custom-fields-component .fi-in-text-item.fi-font-mono{font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.custom-fields-component .fi-in-text-item.fi-color{color:var(--text)}.custom-fields-component .fi-in-text-item.fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component li.fi-in-text-item.fi-color::marker{color:var(--gray-950)}.custom-fields-component li.fi-in-text-item.fi-color:where(.dark,.dark *)::marker{color:var(--color-white)}.custom-fields-component .fi-in-text-item.fi-color-gray{color:var(--gray-500)}.custom-fields-component .fi-in-text-item.fi-color-gray:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component li.fi-in-text-item.fi-color-gray::marker{color:var(--gray-950)}.custom-fields-component .fi-in-text-item>.fi-icon,.custom-fields-component .fi-in-text-item>span:not(.fi-badge)>.fi-icon{color:var(--gray-400);display:inline-block;flex-shrink:0}.custom-fields-component :is(.fi-in-text-item>.fi-icon,.fi-in-text-item>span:not(.fi-badge)>.fi-icon):where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component :is(.fi-in-text-item>.fi-icon,.fi-in-text-item>span:not(.fi-badge)>.fi-icon).fi-color{color:var(--color-500)}.custom-fields-component .fi-no-database{display:flex}.custom-fields-component .fi-no-database .fi-modal-window-ctn>.fi-modal-window .fi-modal-heading{display:inline-block;position:relative}.custom-fields-component .fi-no-database .fi-modal-window-ctn>.fi-modal-window .fi-modal-heading .fi-badge{inset-inline-start:100%;margin-inline-start:calc(var(--spacing)*1);position:absolute;top:calc(var(--spacing)*-1);width:max-content}.custom-fields-component .fi-no-database .fi-modal-window-ctn>.fi-modal-window .fi-modal-header .fi-ac{margin-top:calc(var(--spacing)*2)}.custom-fields-component .fi-no-database .fi-modal-window-ctn>.fi-modal-window .fi-modal-content{margin-inline:calc(var(--spacing)*-6);margin-top:calc(var(--spacing)*-6);row-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-no-database .fi-modal-window-ctn>.fi-modal-window .fi-modal-content>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-no-database .fi-modal-window-ctn>.fi-modal-window .fi-modal-content:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-no-database .fi-modal-window-ctn>.fi-modal-window .fi-modal-content:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-no-database .fi-modal-window-ctn>.fi-modal-window:not(.fi-modal-window-has-footer) .fi-modal-content{margin-bottom:calc(var(--spacing)*-6)}.custom-fields-component .fi-no-database .fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-footer .fi-modal-content{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200)}.custom-fields-component .fi-no-database .fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-footer .fi-modal-content:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-database .fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-footer .fi-modal-content:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-no-database .fi-no-notification-unread-ctn{position:relative}.custom-fields-component .fi-no-database .fi-no-notification-unread-ctn:before{background-color:var(--primary-600);content:var(--tw-content);height:100%;inset-inline-start:calc(var(--spacing)*0);position:absolute;width:calc(var(--spacing)*.5)}.custom-fields-component .fi-no-database .fi-no-notification-unread-ctn:where(.dark,.dark *):before{background-color:var(--primary-500);content:var(--tw-content)}.custom-fields-component .fi-no-notification{gap:calc(var(--spacing)*3);padding:calc(var(--spacing)*4);pointer-events:auto;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));visibility:hidden;width:100%;--tw-duration:.3s;display:flex;flex-shrink:0;overflow:hidden;transition-duration:.3s}.custom-fields-component .fi-no-notification .fi-no-notification-icon{color:var(--gray-400)}.custom-fields-component .fi-no-notification .fi-no-notification-icon.fi-color{color:var(--color-400)}.custom-fields-component .fi-no-notification .fi-no-notification-main{display:grid;flex:1;gap:calc(var(--spacing)*3);margin-top:calc(var(--spacing)*.5)}.custom-fields-component .fi-no-notification .fi-no-notification-text{display:grid;gap:calc(var(--spacing)*1)}.custom-fields-component .fi-no-notification .fi-no-notification-title{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-no-notification .fi-no-notification-title:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-no-notification .fi-no-notification-date{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-no-notification .fi-no-notification-date:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-no-notification .fi-no-notification-body{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));overflow:hidden;overflow-wrap:break-word;text-wrap:pretty}.custom-fields-component .fi-no-notification .fi-no-notification-body:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-no-notification .fi-no-notification-body>p:not(:first-of-type){margin-top:calc(var(--spacing)*1)}.custom-fields-component .fi-no-notification:not(.fi-inline){background-color:var(--color-white);border-radius:var(--radius-xl);gap:calc(var(--spacing)*3);max-width:var(--container-sm);padding:calc(var(--spacing)*4);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent);display:flex}.custom-fields-component .fi-no-notification:not(.fi-inline):where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification:not(.fi-inline):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-no-notification:not(.fi-inline).fi-color{--tw-ring-color:var(--color-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification:not(.fi-inline).fi-color{--tw-ring-color:color-mix(in oklab,var(--color-600)20%,transparent)}}.custom-fields-component .fi-no-notification:not(.fi-inline).fi-color:where(.dark,.dark *){--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification:not(.fi-inline).fi-color:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-400)30%,transparent)}}.custom-fields-component .fi-no-notification:not(.fi-inline).fi-transition-leave-end{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.custom-fields-component .fi-no-notification.fi-color{background-color:#fff}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification.fi-color{background-color:color-mix(in oklab,#fff 90%,var(--color-400))}}.custom-fields-component .fi-no-notification.fi-color:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification.fi-color:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900)90%,var(--color-400))}}.custom-fields-component .fi-no-notification.fi-transition-enter-start,.custom-fields-component .fi-no-notification.fi-transition-leave-end{opacity:0}.custom-fields-component :is(.fi-no.fi-align-start,.fi-no.fi-align-left) .fi-no-notification.fi-transition-enter-start{--tw-translate-x:calc(var(--spacing)*-12);translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component :is(.fi-no.fi-align-end,.fi-no.fi-align-right) .fi-no-notification.fi-transition-enter-start{--tw-translate-x:calc(var(--spacing)*12);translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-no.fi-align-center.fi-vertical-align-start .fi-no-notification.fi-transition-enter-start{--tw-translate-y:calc(var(--spacing)*-12);translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-no.fi-align-center.fi-vertical-align-end .fi-no-notification.fi-transition-enter-start{--tw-translate-y:calc(var(--spacing)*12);translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-no{display:flex;gap:calc(var(--spacing)*3);inset:calc(var(--spacing)*4);margin-inline:auto;pointer-events:none;position:fixed;z-index:50}.custom-fields-component .fi-no.fi-align-left,.custom-fields-component .fi-no.fi-align-start{align-items:flex-start}.custom-fields-component .fi-no.fi-align-center{align-items:center}.custom-fields-component .fi-no.fi-align-end,.custom-fields-component .fi-no.fi-align-right{align-items:flex-end}.custom-fields-component .fi-no.fi-vertical-align-start{flex-direction:column-reverse;justify-content:flex-end}.custom-fields-component .fi-no.fi-vertical-align-center{flex-direction:column;justify-content:center}.custom-fields-component .fi-no.fi-vertical-align-end{flex-direction:column;justify-content:flex-end}.custom-fields-component .fi-sc-actions{display:flex;flex-direction:column;gap:calc(var(--spacing)*2);height:100%}.custom-fields-component .fi-sc-actions .fi-sc-actions-label-ctn{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-sc-actions .fi-sc-actions-label-ctn .fi-sc-actions-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-sc-actions .fi-sc-actions-label-ctn .fi-sc-actions-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sc-actions.fi-sticky .fi-ac{background-color:var(--color-white);bottom:calc(var(--spacing)*0);margin-inline:calc(var(--spacing)*-4);padding:calc(var(--spacing)*4);transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,);width:100%;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent);position:fixed;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}@media (min-width:48rem){.custom-fields-component .fi-sc-actions.fi-sticky .fi-ac{border-radius:var(--radius-xl);bottom:calc(var(--spacing)*4)}}.custom-fields-component .fi-sc-actions.fi-sticky .fi-ac:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-actions.fi-sticky .fi-ac:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-sc-actions.fi-vertical-align-start{justify-content:flex-start}.custom-fields-component .fi-sc-actions.fi-vertical-align-center{justify-content:center}.custom-fields-component .fi-sc-actions.fi-vertical-align-end{justify-content:flex-end}.custom-fields-component .fi-sc-flex{display:flex;gap:calc(var(--spacing)*6)}.custom-fields-component .fi-sc-flex.fi-dense{gap:calc(var(--spacing)*3)}.custom-fields-component .fi-sc-flex>.fi-hidden{display:none}.custom-fields-component .fi-sc-flex>.fi-growable{flex:1;width:100%}.custom-fields-component .fi-sc-flex.fi-from-default{align-items:flex-start}.custom-fields-component .fi-sc-flex.fi-from-default.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-default.fi-vertical-align-end{align-items:flex-end}.custom-fields-component .fi-sc-flex.fi-from-sm{flex-direction:column}@media (min-width:40rem){.custom-fields-component .fi-sc-flex.fi-from-sm{align-items:flex-start;flex-direction:row}.custom-fields-component .fi-sc-flex.fi-from-sm.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-sm.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-sc-flex.fi-from-md{flex-direction:column}@media (min-width:48rem){.custom-fields-component .fi-sc-flex.fi-from-md{align-items:flex-start;flex-direction:row}.custom-fields-component .fi-sc-flex.fi-from-md.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-md.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-sc-flex.fi-from-lg{flex-direction:column}@media (min-width:64rem){.custom-fields-component .fi-sc-flex.fi-from-lg{align-items:flex-start;flex-direction:row}.custom-fields-component .fi-sc-flex.fi-from-lg.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-lg.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-sc-flex.fi-from-xl{flex-direction:column}@media (min-width:80rem){.custom-fields-component .fi-sc-flex.fi-from-xl{align-items:flex-start;flex-direction:row}.custom-fields-component .fi-sc-flex.fi-from-xl.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-xl.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-sc-flex.fi-from-2xl{flex-direction:column}@media (min-width:96rem){.custom-fields-component .fi-sc-flex.fi-from-2xl{align-items:flex-start;flex-direction:row}.custom-fields-component .fi-sc-flex.fi-from-2xl.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-2xl.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-sc-form{display:flex;flex-direction:column;gap:calc(var(--spacing)*6)}.custom-fields-component .fi-sc-form.fi-dense{gap:calc(var(--spacing)*3)}.custom-fields-component .fi-sc-fused-group>.fi-sc{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)10%,transparent)}.custom-fields-component .fi-sc-fused-group>.fi-sc:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component .fi-sc-fused-group>.fi-sc:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-fused-group>.fi-sc:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-sc-fused-group>.fi-sc:where(.dark,.dark *):focus-within{--tw-ring-color:var(--primary-500)}.custom-fields-component :where(.fi-sc-fused-group .fi-sc>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-950);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-sc-fused-group .fi-sc>:not(:last-child)){border-color:color-mix(in oklab,var(--gray-950)10%,transparent)}}.custom-fields-component .fi-sc-fused-group .fi-sc{border-radius:var(--radius-lg)}.custom-fields-component :where(.fi-sc-fused-group .fi-sc:where(.dark,.dark *)>:not(:last-child)){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-sc-fused-group .fi-sc:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-sc-fused-group .fi-sc .fi-sc-component,.custom-fields-component .fi-sc-fused-group .fi-sc .fi-sc-component .fi-fo-field,.custom-fields-component .fi-sc-fused-group .fi-sc .fi-sc-component .fi-input{min-height:100%}.custom-fields-component .fi-sc-fused-group .fi-sc .fi-sc-component .fi-sc-actions{padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-sc-fused-group .fi-sc>:first-child .fi-input-wrp{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc>:last-child .fi-input-wrp{border-bottom-left-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.custom-fields-component :where(.fi-sc-fused-group .fi-sc.fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}@media (min-width:40rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.sm\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.sm\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.sm\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@media (min-width:48rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.md\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.md\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.md\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@media (min-width:64rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.lg\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.lg\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.lg\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@media (min-width:80rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@media (min-width:96rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\32 xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\32 xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\32 xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@3xs\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@3xs\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@3xs\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:18rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@2xs\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@2xs\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@2xs\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:20rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@xs\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@xs\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@xs\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:24rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@sm\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@sm\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@sm\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:28rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@md\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@md\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@md\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:32rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@lg\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@lg\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@lg\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:36rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:42rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@2xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@2xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@2xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:48rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@3xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@3xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@3xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:56rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@4xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@4xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@4xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:64rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@5xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@5xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@5xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:72rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@6xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@6xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@6xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@container (min-width:80rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@7xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@7xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\@7xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\!\@sm\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@sm\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@sm\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@media (min-width:48rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\!\@md\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@md\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@md\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@media (min-width:64rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\!\@lg\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@lg\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@lg\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@media (min-width:80rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\!\@xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}@media (min-width:96rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\!\@2xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-style:var(--tw-border-style);--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@2xl\:fi-grid-cols>:first-child .fi-input-wrp{border-end-start-radius:var(--radius-lg);border-start-end-radius:0}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@2xl\:fi-grid-cols>:last-child .fi-input-wrp{border-end-start-radius:0;border-start-end-radius:var(--radius-lg)}}}.custom-fields-component .fi-sc-fused-group .fi-input-wrp{--tw-shadow:0 0 #0000;--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);border-radius:0;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-sc-fused-group .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)).fi-invalid:focus-within,.custom-fields-component .fi-sc-fused-group .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)):focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-sc-icon{color:var(--gray-400)}.custom-fields-component .fi-sc-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-sc-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-sc-icon.fi-color:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-sc-image{border-color:var(--gray-300);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px}.custom-fields-component .fi-sc-image:where(.dark,.dark *){border-color:#0000}.custom-fields-component .fi-sc-image.fi-align-center{margin-inline:auto}.custom-fields-component .fi-sc-image.fi-align-end,.custom-fields-component .fi-sc-image.fi-align-right{margin-inline-start:auto}.custom-fields-component .fi-sc-section{display:flex;flex-direction:column;gap:calc(var(--spacing)*2)}.custom-fields-component .fi-sc-section .fi-sc-section-label-ctn{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-sc-section .fi-sc-section-label-ctn .fi-sc-section-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-sc-section .fi-sc-section-label-ctn .fi-sc-section-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sc-tabs{display:flex;flex-direction:column}.custom-fields-component .fi-sc-tabs .fi-tabs.fi-invisible{visibility:hidden}.custom-fields-component .fi-sc-tabs .fi-sc-tabs-tab{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-sc-tabs .fi-sc-tabs-tab{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-sc-tabs .fi-sc-tabs-tab.fi-active{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-sc-tabs .fi-sc-tabs-tab:not(.fi-active){height:calc(var(--spacing)*0);overflow:hidden;padding:calc(var(--spacing)*0);position:absolute;visibility:hidden}.custom-fields-component .fi-sc-tabs.fi-contained{background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component .fi-sc-tabs.fi-contained:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-tabs.fi-contained:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-sc-tabs.fi-contained .fi-sc-tabs-tab.fi-active{margin-top:calc(var(--spacing)*0);padding:calc(var(--spacing)*6)}.custom-fields-component .fi-sc-tabs.fi-vertical{flex-direction:row}.custom-fields-component .fi-sc-tabs.fi-vertical .fi-sc-tabs-tab.fi-active{flex:1;margin-inline-start:calc(var(--spacing)*6);margin-top:calc(var(--spacing)*0)}.custom-fields-component .fi-sc-text.fi-copyable{cursor:pointer}.custom-fields-component .fi-sc-text.fi-font-sans{font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.custom-fields-component .fi-sc-text.fi-font-serif{font-family:var(--serif-font-family),ui-serif,Georgia,Cambria,"Times New Roman",Times,serif}.custom-fields-component .fi-sc-text.fi-font-mono{font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.custom-fields-component .fi-sc-text:not(.fi-badge){color:var(--gray-600);display:inline-block;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));overflow-wrap:break-word}.custom-fields-component .fi-sc-text:not(.fi-badge):where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-color-neutral{color:var(--gray-950)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-color-neutral:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-color:not(.fi-color-neutral){color:var(--text)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-color:not(.fi-color-neutral):where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-sc-unordered-list{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));list-style-type:disc;margin-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-sc-unordered-list.fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-sc-unordered-list.fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-sc-unordered-list.fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-sc-wizard{display:flex;flex-direction:column}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header{display:grid}@media (min-width:48rem){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header{grid-auto-flow:column;overflow-x:auto}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step{display:flex;position:relative}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active){display:none}@media (min-width:48rem){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active){display:flex}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn{align-items:center;column-gap:calc(var(--spacing)*4);display:flex;height:100%;padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*6);text-align:start}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn{align-items:center;border-radius:3.40282e+38px;display:flex;flex-shrink:0;height:calc(var(--spacing)*10);justify-content:center;width:calc(var(--spacing)*10)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-number{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text{display:grid;justify-items:start}@media (min-width:48rem){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text{max-width:calc(var(--spacing)*60);width:max-content}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-description{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));text-align:start}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-separator{color:var(--gray-200);display:none;height:100%;inset-inline-end:calc(var(--spacing)*0);position:absolute;width:calc(var(--spacing)*5)}@media (min-width:48rem){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-separator{display:block}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-separator:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:180deg}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-separator:where(.dark,.dark *){color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-separator:where(.dark,.dark *){color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-completed .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn{background-color:var(--primary-600)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-completed .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn:where(.dark,.dark *){background-color:var(--primary-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-completed .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-icon{color:var(--color-white)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-completed .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label{color:var(--gray-950)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-completed .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn{border-style:var(--tw-border-style);border-width:2px}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed).fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-icon{color:var(--primary-600)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed).fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-icon:where(.dark,.dark *){color:var(--primary-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed):not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed):not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn{border-color:var(--primary-600)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn:where(.dark,.dark *){border-color:var(--primary-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-number{color:var(--primary-600)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-number:where(.dark,.dark *){color:var(--primary-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label{color:var(--primary-700)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn{border-color:var(--gray-300)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn:where(.dark,.dark *){border-color:var(--gray-600)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-number{color:var(--gray-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-number:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label{color:var(--gray-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-step{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-step{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-step:not(.fi-active){height:calc(var(--spacing)*0);overflow:hidden;padding:calc(var(--spacing)*0);position:absolute;visibility:hidden}.custom-fields-component .fi-sc-wizard:not(.fi-sc-wizard-header-hidden) .fi-sc-wizard-step.fi-active{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-footer{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;justify-content:space-between}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-footer>.fi-hidden{display:none}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-footer>.fi-disabled{opacity:.7;pointer-events:none}.custom-fields-component .fi-sc-wizard.fi-contained{background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component .fi-sc-wizard.fi-contained:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-wizard.fi-contained:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-sc-wizard.fi-contained .fi-sc-wizard-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200)}.custom-fields-component .fi-sc-wizard.fi-contained .fi-sc-wizard-header:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-wizard.fi-contained .fi-sc-wizard-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-sc-wizard.fi-contained .fi-sc-wizard-step.fi-active{margin-top:calc(var(--spacing)*0);padding:calc(var(--spacing)*6)}.custom-fields-component .fi-sc-wizard.fi-contained .fi-sc-wizard-footer{padding-inline:calc(var(--spacing)*6);padding-bottom:calc(var(--spacing)*6)}.custom-fields-component .fi-sc-wizard:not(.fi-contained) .fi-sc-wizard-header{background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component .fi-sc-wizard:not(.fi-contained) .fi-sc-wizard-header:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-wizard:not(.fi-contained) .fi-sc-wizard-header:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-sc-wizard:not(.fi-contained) .fi-sc-wizard-footer{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-sc{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-sc.fi-inline{align-items:center;display:flex;flex-grow:1;flex-wrap:wrap}.custom-fields-component .fi-sc.fi-sc-has-gap{gap:calc(var(--spacing)*6)}.custom-fields-component .fi-sc.fi-sc-has-gap.fi-sc-dense{gap:calc(var(--spacing)*3)}.custom-fields-component .fi-sc.fi-align-left,.custom-fields-component .fi-sc.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-sc.fi-align-center{justify-content:center}.custom-fields-component .fi-sc.fi-align-end,.custom-fields-component .fi-sc.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-sc.fi-align-between,.custom-fields-component .fi-sc.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-sc>.fi-hidden{display:none}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-xs{max-width:var(--container-xs)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-sm{max-width:var(--container-sm)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-md{max-width:var(--container-md)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-lg{max-width:var(--container-lg)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-xl{max-width:var(--container-xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-2xl{max-width:var(--container-2xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-3xl{max-width:var(--container-3xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-4xl{max-width:var(--container-4xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-5xl{max-width:var(--container-5xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-6xl{max-width:var(--container-6xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-7xl{max-width:var(--container-7xl)}.custom-fields-component .fi-sc>.fi-grid-col>.fi-sc-component{height:100%}.custom-fields-component .fi-ta-actions{align-items:center;display:flex;flex-shrink:0;gap:calc(var(--spacing)*3);justify-content:flex-end;max-width:100%}.custom-fields-component .fi-ta-actions>*{flex-shrink:0}.custom-fields-component .fi-ta-actions.fi-wrapped{flex-wrap:wrap}@media (min-width:40rem){.custom-fields-component .fi-ta-actions.sm\:fi-not-wrapped{flex-wrap:nowrap}}.custom-fields-component .fi-ta-actions.fi-align-center{justify-content:center}.custom-fields-component .fi-ta-actions.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-ta-actions.fi-align-between{justify-content:space-between}@media (min-width:48rem){.custom-fields-component .fi-ta-actions.md\:fi-align-end{justify-content:flex-end}}.custom-fields-component .fi-ta-cell{padding:calc(var(--spacing)*0)}.custom-fields-component .fi-ta-cell:first-of-type{padding-inline-start:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-cell:last-of-type{padding-inline-end:calc(var(--spacing)*1)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell:first-of-type{padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-cell:last-of-type{padding-inline-end:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-cell.fi-vertical-align-start{vertical-align:top}.custom-fields-component .fi-ta-cell.fi-vertical-align-end{vertical-align:bottom}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.sm\:fi-hidden{display:none}}@media (min-width:48rem){.custom-fields-component .fi-ta-cell.md\:fi-hidden{display:none}}@media (min-width:64rem){.custom-fields-component .fi-ta-cell.lg\:fi-hidden{display:none}}@media (min-width:80rem){.custom-fields-component .fi-ta-cell.xl\:fi-hidden{display:none}}@media (min-width:96rem){.custom-fields-component .fi-ta-cell.\32 xl\:fi-hidden{display:none}}.custom-fields-component .fi-ta-cell.sm\:fi-visible{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.sm\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-cell.md\:fi-visible{display:none}@media (min-width:48rem){.custom-fields-component .fi-ta-cell.md\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-cell.lg\:fi-visible{display:none}@media (min-width:64rem){.custom-fields-component .fi-ta-cell.lg\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-cell.xl\:fi-visible{display:none}@media (min-width:80rem){.custom-fields-component .fi-ta-cell.xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-cell.\32 xl\:fi-visible{display:none}@media (min-width:96rem){.custom-fields-component .fi-ta-cell.\32 xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-cell>.fi-ta-col{display:flex;justify-content:flex-start;text-align:start;width:100%}.custom-fields-component .fi-ta-cell>.fi-ta-col:disabled{pointer-events:none}.custom-fields-component .fi-ta-cell:has(.fi-ta-reorder-handle){padding-inline:calc(var(--spacing)*3);width:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-cell:has(.fi-ta-reorder-handle):first-of-type{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell:has(.fi-ta-reorder-handle):last-of-type{padding-inline-end:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell:has(.fi-ta-reorder-handle):first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell:has(.fi-ta-reorder-handle):last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell:has(.fi-ta-actions){padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3);white-space:nowrap}.custom-fields-component .fi-ta-cell:has(.fi-ta-actions):first-of-type{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell:has(.fi-ta-actions):last-of-type{padding-inline-end:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell:has(.fi-ta-actions):first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell:has(.fi-ta-actions):last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell:has(.fi-ta-record-checkbox){padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3);width:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-cell:has(.fi-ta-record-checkbox):first-of-type{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell:has(.fi-ta-record-checkbox):last-of-type{padding-inline-end:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell:has(.fi-ta-record-checkbox):first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell:has(.fi-ta-record-checkbox):last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell .fi-ta-placeholder{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);color:var(--gray-400);line-height:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell .fi-ta-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-cell.fi-ta-summary-row-heading-cell{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3);--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.fi-ta-summary-row-heading-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell.fi-ta-summary-row-heading-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell.fi-ta-summary-row-heading-cell:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-cell.fi-align-start{text-align:start}.custom-fields-component .fi-ta-cell.fi-align-center{text-align:center}.custom-fields-component .fi-ta-cell.fi-align-end{text-align:end}.custom-fields-component .fi-ta-cell.fi-align-left{text-align:left}.custom-fields-component .fi-ta-cell.fi-align-right{text-align:right}.custom-fields-component .fi-ta-cell.fi-align-between,.custom-fields-component .fi-ta-cell.fi-align-justify{text-align:justify}.custom-fields-component .fi-ta-cell.fi-ta-summary-header-cell{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3);--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.fi-ta-summary-header-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell.fi-ta-summary-header-cell:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-cell.fi-ta-summary-header-cell.fi-wrapped,.custom-fields-component .fi-ta-cell.fi-ta-summary-header-cell:not(.fi-wrapped){white-space:nowrap}.custom-fields-component .fi-ta-cell.fi-ta-individual-search-cell{min-width:calc(var(--spacing)*48);padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-cell .fi-ta-reorder-handle{cursor:move}.custom-fields-component .fi-ta-cell.fi-ta-selection-cell{padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3);width:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-cell.fi-ta-selection-cell:first-of-type{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell.fi-ta-selection-cell:last-of-type{padding-inline-end:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.fi-ta-selection-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell.fi-ta-selection-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell.fi-ta-group-selection-cell{padding-inline:calc(var(--spacing)*3);width:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-cell.fi-ta-group-selection-cell:first-of-type{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell.fi-ta-group-selection-cell:last-of-type{padding-inline-end:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.fi-ta-group-selection-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell.fi-ta-group-selection-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-checkbox{width:100%}.custom-fields-component .fi-ta-checkbox:not(.fi-inline){padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-checkbox.fi-align-center{text-align:center}.custom-fields-component .fi-ta-checkbox.fi-align-end,.custom-fields-component .fi-ta-checkbox.fi-align-right{text-align:end}.custom-fields-component .fi-ta-color{display:flex;gap:calc(var(--spacing)*1.5);width:100%}.custom-fields-component .fi-ta-color.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-ta-color:not(.fi-inline){padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-color.fi-align-left,.custom-fields-component .fi-ta-color.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-ta-color.fi-align-center{justify-content:center}.custom-fields-component .fi-ta-color.fi-align-end,.custom-fields-component .fi-ta-color.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-ta-color.fi-align-between,.custom-fields-component .fi-ta-color.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-ta-color>.fi-ta-color-item{border-radius:var(--radius-md);height:calc(var(--spacing)*6);width:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-color>.fi-ta-color-item.fi-copyable{cursor:pointer}.custom-fields-component .fi-ta-icon{display:flex;gap:calc(var(--spacing)*1.5);width:100%}.custom-fields-component .fi-ta-icon.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-ta-icon.fi-ta-icon-has-line-breaks{flex-direction:column}.custom-fields-component .fi-ta-icon:not(.fi-inline){padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-icon.fi-align-left,.custom-fields-component .fi-ta-icon.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-ta-icon.fi-align-center{justify-content:center}.custom-fields-component .fi-ta-icon.fi-align-end,.custom-fields-component .fi-ta-icon.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-ta-icon.fi-align-between,.custom-fields-component .fi-ta-icon.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-ta-icon>.fi-icon,.custom-fields-component .fi-ta-icon>a>.fi-icon{color:var(--gray-400)}.custom-fields-component :is(.fi-ta-icon>.fi-icon,.fi-ta-icon>a>.fi-icon):where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component :is(.fi-ta-icon>.fi-icon,.fi-ta-icon>a>.fi-icon).fi-color{color:var(--text)}.custom-fields-component :is(.fi-ta-icon>.fi-icon,.fi-ta-icon>a>.fi-icon).fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-ta-image{align-items:center;display:flex;gap:calc(var(--spacing)*1.5);width:100%}.custom-fields-component .fi-ta-image img{max-width:none;object-fit:cover;object-position:center}.custom-fields-component .fi-ta-image.fi-circular img{border-radius:3.40282e+38px}.custom-fields-component .fi-ta-image.fi-ta-image-ring .fi-ta-image-limited-remaining-text,.custom-fields-component .fi-ta-image.fi-ta-image-ring img{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-white)}.custom-fields-component :is(.fi-ta-image.fi-ta-image-ring img,.fi-ta-image.fi-ta-image-ring .fi-ta-image-limited-remaining-text):where(.dark,.dark *){--tw-ring-color:var(--gray-900)}.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-1 .fi-ta-image-limited-remaining-text,.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-1 img{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-2 .fi-ta-image-limited-remaining-text,.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-2 img{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-4 .fi-ta-image-limited-remaining-text,.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-4 img{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(4px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-1{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-1*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-1*var(--tw-space-x-reverse))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-2{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-2*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-2*var(--tw-space-x-reverse))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-3{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-3*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-3*var(--tw-space-x-reverse))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-4{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-4*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-4*var(--tw-space-x-reverse))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-5{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-5*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-5*var(--tw-space-x-reverse))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-6{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-6*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-6*var(--tw-space-x-reverse))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-7{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-7>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-7*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-7*var(--tw-space-x-reverse))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-8{column-gap:calc(var(--spacing)*0)}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-end:calc(var(--spacing)*-8*(1 - var(--tw-space-x-reverse)));margin-inline-start:calc(var(--spacing)*-8*var(--tw-space-x-reverse))}.custom-fields-component .fi-ta-image.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-ta-image:not(.fi-inline){padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-image.fi-align-left,.custom-fields-component .fi-ta-image.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-ta-image.fi-align-center{justify-content:center}.custom-fields-component .fi-ta-image.fi-align-end,.custom-fields-component .fi-ta-image.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-ta-image.fi-align-between,.custom-fields-component .fi-ta-image.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-ta-image.fi-stacked .fi-ta-image-limited-remaining-text{background-color:var(--gray-100);border-radius:3.40282e+38px}.custom-fields-component .fi-ta-image.fi-stacked .fi-ta-image-limited-remaining-text:where(.dark,.dark *){background-color:var(--gray-800)}.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);align-items:center;color:var(--gray-500);display:flex;font-weight:var(--font-weight-medium);justify-content:center}.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text.fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text.fi-size-base,.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text.fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text.fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-ta-select{min-width:calc(var(--spacing)*48);width:100%}.custom-fields-component .fi-ta-select:not(.fi-inline){padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-text{width:100%}.custom-fields-component .fi-ta-text.fi-ta-text-has-descriptions,.custom-fields-component .fi-ta-text.fi-ta-text-list-limited{display:flex;flex-direction:column}.custom-fields-component :is(.fi-ta-text.fi-ta-text-has-descriptions,.fi-ta-text.fi-ta-text-list-limited).fi-ta-text-has-badges{row-gap:calc(var(--spacing)*2)}.custom-fields-component :is(.fi-ta-text.fi-ta-text-has-descriptions,.fi-ta-text.fi-ta-text-list-limited):not(.fi-ta-text-has-badges){row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-text:not(.fi-inline){padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-text.fi-bulleted ul,.custom-fields-component ul.fi-ta-text.fi-bulleted{list-style-position:inside;list-style-type:disc}.custom-fields-component .fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges ul,.custom-fields-component ul.fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges{column-gap:calc(var(--spacing)*1.5);display:flex}.custom-fields-component :is(ul.fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges,.fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges ul).fi-wrapped,.custom-fields-component :is(ul.fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges,.fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges ul):is(.fi-wrapped ul){flex-wrap:wrap;row-gap:calc(var(--spacing)*1)}.custom-fields-component :is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul).fi-ta-text-has-line-breaks,.custom-fields-component :is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):is(.fi-ta-text-has-line-breaks ul){display:flex;flex-direction:column;row-gap:calc(var(--spacing)*1)}.custom-fields-component :is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(.fi-ta-text-has-line-breaks ul),.custom-fields-component :is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(ul.fi-ta-text-has-line-breaks){column-gap:calc(var(--spacing)*1.5);display:flex}.custom-fields-component :is(:is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(ul.fi-ta-text-has-line-breaks),:is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(.fi-ta-text-has-line-breaks ul)).fi-wrapped,.custom-fields-component :is(:is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(ul.fi-ta-text-has-line-breaks),:is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(.fi-ta-text-has-line-breaks ul)):is(.fi-wrapped ul){flex-wrap:wrap;row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-text.fi-wrapped:not(.fi-ta-text-has-badges.fi-ta-text-has-line-breaks){white-space:normal}.custom-fields-component .fi-ta-text.fi-wrapped:not(.fi-ta-text-has-badges.fi-ta-text-has-line-breaks) .fi-badge,.custom-fields-component .fi-ta-text.fi-wrapped:not(.fi-ta-text-has-badges.fi-ta-text-has-line-breaks) .fi-ta-text-list-limited-message{white-space:nowrap}.custom-fields-component .fi-ta-text>.fi-ta-text-description,.custom-fields-component .fi-ta-text>.fi-ta-text-list-limited-message{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component :is(.fi-ta-text>.fi-ta-text-description,.fi-ta-text>.fi-ta-text-list-limited-message):where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-text.fi-align-center{text-align:center}.custom-fields-component .fi-ta-text.fi-align-center ul,.custom-fields-component ul.fi-ta-text.fi-align-center{justify-content:center}.custom-fields-component .fi-ta-text.fi-align-end,.custom-fields-component .fi-ta-text.fi-align-right{text-align:end}.custom-fields-component :is(.fi-ta-text.fi-align-end,.fi-ta-text.fi-align-right) ul,.custom-fields-component ul:is(.fi-ta-text.fi-align-end,.fi-ta-text.fi-align-right){justify-content:flex-end}.custom-fields-component .fi-ta-text.fi-align-between,.custom-fields-component .fi-ta-text.fi-align-justify{text-align:justify}.custom-fields-component :is(.fi-ta-text.fi-align-justify,.fi-ta-text.fi-align-between) ul,.custom-fields-component ul:is(.fi-ta-text.fi-align-justify,.fi-ta-text.fi-align-between){justify-content:space-between}.custom-fields-component .fi-ta-text-item{color:var(--gray-950)}.custom-fields-component .fi-ta-text-item:where(.dark,.dark *){color:var(--color-white)}@media (hover:hover){.custom-fields-component .fi-ta-text-item a:hover{text-decoration-line:underline}}.custom-fields-component .fi-ta-text-item a:focus-visible{text-decoration-line:underline}.custom-fields-component .fi-ta-text-item:not(.fi-bulleted li.fi-ta-text-item){-webkit-line-clamp:var(--line-clamp,none);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.custom-fields-component .fi-ta-text-item>.fi-copyable{cursor:pointer}.custom-fields-component .fi-ta-text-item.fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-ta-text-item.fi-size-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-text-item.fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-ta-text-item.fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-ta-text-item.fi-font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.custom-fields-component .fi-ta-text-item.fi-font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.custom-fields-component .fi-ta-text-item.fi-font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.custom-fields-component .fi-ta-text-item.fi-font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.custom-fields-component .fi-ta-text-item.fi-font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-text-item.fi-font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-ta-text-item.fi-font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.custom-fields-component .fi-ta-text-item.fi-font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.custom-fields-component .fi-ta-text-item.fi-font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.custom-fields-component .fi-ta-text-item.fi-font-sans{font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.custom-fields-component .fi-ta-text-item.fi-font-serif{font-family:var(--serif-font-family),ui-serif,Georgia,Cambria,"Times New Roman",Times,serif}.custom-fields-component .fi-ta-text-item.fi-font-mono{font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.custom-fields-component .fi-ta-text-item.fi-color{color:var(--text)}.custom-fields-component .fi-ta-text-item.fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component li.fi-ta-text-item.fi-color::marker{color:var(--gray-950)}.custom-fields-component li.fi-ta-text-item.fi-color:where(.dark,.dark *)::marker{color:var(--color-white)}.custom-fields-component .fi-ta-text-item.fi-color-gray{color:var(--gray-500)}.custom-fields-component .fi-ta-text-item.fi-color-gray:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component li.fi-ta-text-item.fi-color-gray::marker{color:var(--gray-950)}.custom-fields-component li.fi-ta-text-item.fi-color-gray:where(.dark,.dark *)::marker{color:var(--color-white)}.custom-fields-component .fi-ta-text-item>.fi-icon,.custom-fields-component .fi-ta-text-item>span:not(.fi-badge)>.fi-icon{color:var(--gray-400);display:inline-block;flex-shrink:0}.custom-fields-component :is(.fi-ta-text-item>.fi-icon,.fi-ta-text-item>span:not(.fi-badge)>.fi-icon):where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component :is(.fi-ta-text-item>.fi-icon,.fi-ta-text-item>span:not(.fi-badge)>.fi-icon).fi-color{color:var(--color-500)}.custom-fields-component .fi-ta-text-item.fi-ta-text-has-badges>.fi-badge{vertical-align:middle}@media (hover:hover){.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item:hover{text-decoration-line:underline}}.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item:focus-visible{text-decoration-line:underline}@media (hover:hover){.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item .fi-icon:hover{text-decoration-line:none}}.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item .fi-icon:focus-visible{text-decoration-line:none}@media (hover:hover){.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item>.fi-badge:hover{text-decoration-line:none}}.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item>.fi-badge:focus-visible{text-decoration-line:none}.custom-fields-component .fi-ta-text-input{min-width:calc(var(--spacing)*48);width:100%}.custom-fields-component .fi-ta-text-input:not(.fi-inline){padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-toggle{width:100%}.custom-fields-component .fi-ta-toggle:not(.fi-inline){padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-toggle.fi-align-center{text-align:center}.custom-fields-component .fi-ta-toggle.fi-align-end,.custom-fields-component .fi-ta-toggle.fi-align-right{text-align:end}.custom-fields-component .fi-ta-grid.fi-gap-sm{gap:calc(var(--spacing)*1)}@media (min-width:40rem){.custom-fields-component .fi-ta-grid.sm\:fi-gap-sm{gap:calc(var(--spacing)*1)}}@media (min-width:48rem){.custom-fields-component .fi-ta-grid.md\:fi-gap-sm{gap:calc(var(--spacing)*1)}}@media (min-width:64rem){.custom-fields-component .fi-ta-grid.lg\:fi-gap-sm{gap:calc(var(--spacing)*1)}}@media (min-width:80rem){.custom-fields-component .fi-ta-grid.xl\:fi-gap-sm{gap:calc(var(--spacing)*1)}}@media (min-width:96rem){.custom-fields-component .fi-ta-grid.\32 xl\:fi-gap-sm{gap:calc(var(--spacing)*1)}}.custom-fields-component .fi-ta-grid.fi-gap-lg{gap:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-ta-grid.sm\:fi-gap-lg{gap:calc(var(--spacing)*3)}}@media (min-width:48rem){.custom-fields-component .fi-ta-grid.md\:fi-gap-lg{gap:calc(var(--spacing)*3)}}@media (min-width:64rem){.custom-fields-component .fi-ta-grid.lg\:fi-gap-lg{gap:calc(var(--spacing)*3)}}@media (min-width:80rem){.custom-fields-component .fi-ta-grid.xl\:fi-gap-lg{gap:calc(var(--spacing)*3)}}@media (min-width:96rem){.custom-fields-component .fi-ta-grid.\32 xl\:fi-gap-lg{gap:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-panel{background-color:var(--gray-50);border-radius:var(--radius-lg);padding:calc(var(--spacing)*4);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-panel{--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}}.custom-fields-component .fi-ta-panel{--tw-ring-inset:inset}.custom-fields-component .fi-ta-panel:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-panel:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-panel:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-panel:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-split{display:flex}.custom-fields-component .fi-ta-split.default\:fi-ta-split{align-items:center;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-split.\32 xl\:fi-ta-split,.custom-fields-component .fi-ta-split.lg\:fi-ta-split,.custom-fields-component .fi-ta-split.md\:fi-ta-split,.custom-fields-component .fi-ta-split.sm\:fi-ta-split,.custom-fields-component .fi-ta-split.xl\:fi-ta-split{flex-direction:column;gap:calc(var(--spacing)*2)}@media (min-width:40rem){.custom-fields-component .fi-ta-split.sm\:fi-ta-split{align-items:center;flex-direction:row;gap:calc(var(--spacing)*3)}}@media (min-width:48rem){.custom-fields-component .fi-ta-split.md\:fi-ta-split{align-items:center;flex-direction:row;gap:calc(var(--spacing)*3)}}@media (min-width:64rem){.custom-fields-component .fi-ta-split.lg\:fi-ta-split{align-items:center;flex-direction:row;gap:calc(var(--spacing)*3)}}@media (min-width:80rem){.custom-fields-component .fi-ta-split.xl\:fi-ta-split{align-items:center;flex-direction:row;gap:calc(var(--spacing)*3)}}@media (min-width:96rem){.custom-fields-component .fi-ta-split.\32 xl\:fi-ta-split{align-items:center;flex-direction:row;gap:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-stack{display:flex;flex-direction:column}.custom-fields-component .fi-ta-stack.fi-align-left,.custom-fields-component .fi-ta-stack.fi-align-start{align-items:flex-start}.custom-fields-component .fi-ta-stack.fi-align-center{align-items:center}.custom-fields-component .fi-ta-stack.fi-align-end,.custom-fields-component .fi-ta-stack.fi-align-right{align-items:flex-end}.custom-fields-component :where(.fi-ta-stack.fi-gap-sm>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*1*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*1*var(--tw-space-y-reverse))}.custom-fields-component :where(.fi-ta-stack.fi-gap-md>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse))}.custom-fields-component :where(.fi-ta-stack.fi-gap-lg>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*3*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*3*var(--tw-space-y-reverse))}.custom-fields-component .fi-ta-icon-count-summary{color:var(--gray-500);display:grid;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3);row-gap:calc(var(--spacing)*1.5)}.custom-fields-component .fi-ta-icon-count-summary:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-icon-count-summary>.fi-ta-icon-count-summary-label{--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-icon-count-summary>.fi-ta-icon-count-summary-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-icon-count-summary>ul{display:grid;row-gap:calc(var(--spacing)*1.5)}.custom-fields-component .fi-ta-icon-count-summary>ul>li{align-items:center;column-gap:calc(var(--spacing)*1.5);display:flex;justify-content:flex-end}.custom-fields-component .fi-ta-icon-count-summary>ul>li>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-ta-icon-count-summary>ul>li>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-icon-count-summary>ul>li>.fi-icon.fi-color{color:var(--text)}.custom-fields-component .fi-ta-icon-count-summary>ul>li>.fi-icon.fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-ta-range-summary{color:var(--gray-500);display:grid;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3);row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-range-summary:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-range-summary>.fi-ta-range-summary-label{--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-range-summary>.fi-ta-range-summary-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-text-summary{color:var(--gray-500);display:grid;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3);row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-text-summary:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-text-summary>.fi-ta-text-summary-label{--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-text-summary>.fi-ta-text-summary-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-values-summary{color:var(--gray-500);display:grid;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*3);row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-values-summary:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-values-summary>.fi-ta-values-summary-label{--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-values-summary>.fi-ta-values-summary-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-values-summary>ul.fi-bulleted{list-style-position:inside;list-style-type:disc}.custom-fields-component .fi-ta-ctn{background-color:var(--color-white);border-radius:var(--radius-xl);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent);display:flex;position:relative}.custom-fields-component .fi-ta-ctn:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-ctn:not(.fi-ta-ctn-with-header){overflow:hidden}.custom-fields-component .fi-ta-ctn.fi-loading{animation:var(--animate-pulse)}.custom-fields-component .fi-ta-ctn .fi-ta-header-ctn{margin-top:-1px}.custom-fields-component .fi-ta-ctn .fi-ta-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);display:flex;flex-direction:column;gap:calc(var(--spacing)*3);padding:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-header:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header.fi-ta-header-adaptive-actions-position{align-items:center;flex-direction:row}.custom-fields-component .fi-ta-ctn .fi-ta-header.fi-ta-header-adaptive-actions-position .fi-ta-actions{margin-inline-start:auto}}.custom-fields-component .fi-ta-ctn .fi-ta-header.fi-ta-header-adaptive-actions-position:not(:has(.fi-ta-header-heading)):not(:has(.fi-ta-header-description)) .fi-ta-actions{margin-inline-start:auto}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-header-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-header-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-header-description{color:var(--gray-600);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));margin-top:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-header-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar{align-items:center;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*4);justify-content:space-between;padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar>*{align-items:center;column-gap:calc(var(--spacing)*4);display:flex}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar>:first-child{flex-shrink:0}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar>:nth-child(2){margin-inline-start:auto}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings .fi-dropdown.sm\:fi-hidden{display:none}}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings .fi-dropdown .fi-ta-grouping-settings-fields{display:grid;padding:calc(var(--spacing)*6);row-gap:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings .fi-dropdown .fi-ta-grouping-settings-fields label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));row-gap:calc(var(--spacing)*2);--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);display:grid;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings .fi-dropdown .fi-ta-grouping-settings-fields label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings>.fi-ta-grouping-settings-fields{align-items:center;column-gap:calc(var(--spacing)*3);display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings>.fi-ta-grouping-settings-fields{display:flex}}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-col-manager-dropdown .fi-ta-col-manager,.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-filters-dropdown .fi-ta-filters{padding:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-ctn .fi-ta-filters{display:grid;row-gap:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-ctn .fi-ta-filters.fi-ta-filters-below-content{border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:1px;padding:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters.fi-ta-filters-below-content{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters.fi-ta-filters-below-content:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-filters.fi-ta-filters-below-content:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-header{align-items:center;display:flex;justify-content:space-between}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-header .fi-ta-filters-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-header .fi-ta-filters-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-header .fi-loading-indicator{color:var(--gray-400)}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-header .fi-loading-indicator:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-actions-ctn{display:flex;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);display:grid;padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn .fi-ta-filters-trigger-action-ctn{margin-inline-start:auto}.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn.fi-open .fi-ta-filters-trigger-action-ctn{margin-top:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn.fi-open:has(.fi-ta-filters-actions-ctn) .fi-ta-filters-trigger-action-ctn{margin-top:calc(var(--spacing)*-7)}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator{align-items:center;background-color:var(--gray-50);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);column-gap:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*3);--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);display:flex;font-weight:var(--font-weight-medium)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator .fi-loading-indicator{color:var(--gray-400)}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator .fi-loading-indicator:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator{background-color:var(--gray-50);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);display:flex;flex-direction:column;justify-content:space-between;padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3);row-gap:calc(var(--spacing)*1)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator{align-items:center;flex-direction:row;padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator .fi-loading-indicator{color:var(--gray-400)}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator .fi-loading-indicator:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator .fi-ta-selection-indicator-actions-ctn,.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator>*{column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator>:first-child{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator>:first-child:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator>:nth-child(2){margin-inline-start:auto}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators{align-items:flex-start;background-color:var(--gray-50);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);column-gap:calc(var(--spacing)*3);display:flex;justify-content:space-between;padding-block:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:first-child{column-gap:calc(var(--spacing)*3);display:flex;flex-direction:column;row-gap:calc(var(--spacing)*1)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:first-child{flex-direction:row}}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:first-child .fi-ta-filter-indicators-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);font-weight:var(--font-weight-medium);white-space:nowrap}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:first-child .fi-ta-filter-indicators-label:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:first-child .fi-ta-filter-indicators-badges-ctn{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*1.5)}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:nth-child(2).fi-icon-btn{margin-top:calc(var(--spacing)*-1)}.custom-fields-component .fi-ta-ctn .fi-pagination{border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:1px;padding-block:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-pagination{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-pagination:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-pagination:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-table-loading-ctn{align-items:center;display:flex;height:calc(var(--spacing)*32);justify-content:center}.custom-fields-component .fi-ta-ctn .fi-ta-main{flex:1;min-width:calc(var(--spacing)*0)}@media (min-width:64rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters-trigger-action-ctn.lg\:fi-hidden{display:none}}.custom-fields-component .fi-ta-ctn .fi-ta-filters-after-content-ctn,.custom-fields-component .fi-ta-ctn .fi-ta-filters-before-content-ctn{background-color:var(--color-white);border-color:var(--gray-200);border-radius:var(--radius-lg);z-index:20;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);width:100vw;--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent);display:none;flex-shrink:0;max-width:14rem!important;position:absolute;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}@media (min-width:64rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters-after-content-ctn,.custom-fields-component .fi-ta-ctn .fi-ta-filters-before-content-ctn{z-index:auto;--tw-shadow:0 0 #0000;--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:static}}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn):where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn):where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn):where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn) .fi-ta-filters{padding:calc(var(--spacing)*6)}@media (min-width:64rem){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn) .fi-ta-filters{padding-block:calc(var(--spacing)*4)}}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-open{display:block}@media (min-width:64rem){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).lg\:fi-open{display:block}}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-opacity-0{opacity:0}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-xs{max-width:var(--container-xs)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-sm{max-width:var(--container-sm)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-md{max-width:var(--container-md)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-lg{max-width:var(--container-lg)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-xl{max-width:var(--container-xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-2xl{max-width:var(--container-2xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-3xl{max-width:var(--container-3xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-4xl{max-width:var(--container-4xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-5xl{max-width:var(--container-5xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-6xl{max-width:var(--container-6xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-7xl{max-width:var(--container-7xl)!important}.custom-fields-component .fi-ta-ctn .fi-ta-filters-before-content-ctn{inset-inline-start:calc(var(--spacing)*0)}@media (min-width:64rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters-before-content-ctn{border-end-end-radius:0;border-end-start-radius:var(--radius-xl);border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px;border-start-end-radius:0;border-start-start-radius:var(--radius-xl)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters-after-content-ctn{inset-inline-end:calc(var(--spacing)*0)}@media (min-width:64rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters-after-content-ctn{border-end-end-radius:var(--radius-xl);border-end-start-radius:0;border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px;border-start-end-radius:var(--radius-xl);border-start-start-radius:0}}.custom-fields-component .fi-ta-content-ctn{position:relative}.custom-fields-component :where(.fi-ta-content-ctn>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-ta-content-ctn{overflow-x:auto}.custom-fields-component :where(.fi-ta-content-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-ta-content-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-content-ctn:where(.dark,.dark *){border-top-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn:where(.dark,.dark *){border-top-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header{align-items:center;background-color:var(--gray-50);gap:calc(var(--spacing)*4);column-gap:calc(var(--spacing)*6);display:flex;padding-inline:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header .fi-ta-page-checkbox{flex-shrink:0;margin-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header .fi-ta-sorting-settings{column-gap:calc(var(--spacing)*3);display:flex;padding-block:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-content-ctn:not(.fi-ta-ctn-with-footer .fi-ta-content-ctn){border-bottom-left-radius:var(--radius-xl);border-bottom-right-radius:var(--radius-xl)}.custom-fields-component .fi-ta-content-ctn:not(.fi-ta-ctn-with-header .fi-ta-content-ctn){border-top-style:var(--tw-border-style);border-top-width:0}.custom-fields-component .fi-ta-content-ctn .fi-ta-content{display:grid}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid{gap:calc(var(--spacing)*4);padding:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid.fi-ta-content-grouped{padding-top:calc(var(--spacing)*0)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header{border-block-style:var(--tw-border-style);border-block-width:1px;border-color:var(--gray-200);margin-inline:calc(var(--spacing)*-4)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header:first-child{border-top-style:var(--tw-border-style);border-top-width:0}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header{margin-inline:calc(var(--spacing)*-6)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header{width:calc(100% + 2rem)}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header{width:calc(100% + 3rem)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record{border-radius:var(--radius-xl);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}@media (hover:hover){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-selected:where(.dark,.dark *){background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-selected:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-selected:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-selected:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:not(.fi-selected){background-color:var(--color-white)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:not(.fi-selected):where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:not(.fi-selected):where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:not(.fi-selected):where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:not(.fi-selected):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-ta-record-with-content-prefix .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-ta-record-with-content-prefix .fi-ta-record-content{padding-inline-start:calc(var(--spacing)*2)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-ta-record-with-content-suffix .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-ta-record-with-content-suffix .fi-ta-record-content{padding-inline-end:calc(var(--spacing)*2)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid){background-color:var(--gray-200);row-gap:1px}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid):where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid):where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}@media (hover:hover){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-selected:before{background-color:var(--primary-600);content:var(--tw-content);inset-block:calc(var(--spacing)*0);inset-inline-start:calc(var(--spacing)*0);position:absolute;width:calc(var(--spacing)*.5)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-selected:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-selected:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-selected:where(.dark,.dark *):before{background-color:var(--primary-500);content:var(--tw-content)}@media (min-width:48rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record .fi-ta-record-content-ctn{align-items:center;flex-direction:row}}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record:not(.fi-ta-record-with-content-prefix) .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record:not(.fi-ta-record-with-content-prefix) .fi-ta-record-content{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record:not(.fi-ta-record-with-content-suffix) .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record:not(.fi-ta-record-with-content-suffix) .fi-ta-record-content{padding-inline-end:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-prefix{padding-inline-start:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-prefix .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-prefix .fi-ta-record-content{padding-inline-start:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-suffix{padding-inline-end:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-suffix .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-suffix .fi-ta-record-content{padding-inline-end:calc(var(--spacing)*3)}@media (min-width:48rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record .fi-ta-actions{padding-inline-start:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header{align-items:center;background-color:var(--gray-50);column-gap:calc(var(--spacing)*3);display:flex;grid-column:1/-1;padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*1);width:100%}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header{padding-inline:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header.fi-collapsible{cursor:pointer}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header.fi-collapsible.fi-collapsed .fi-icon-btn{rotate:-180deg}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header .fi-ta-group-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header .fi-ta-group-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header .fi-ta-group-description{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header .fi-ta-group-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header .fi-ta-group-checkbox{margin-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-table{grid-column:1/-1}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record{background-color:var(--color-white);height:100%;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;align-items:center;display:flex;position:relative;transition-duration:75ms}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-ta-record-with-content-prefix{padding-inline-start:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-ta-record-with-content-suffix{padding-inline-end:calc(var(--spacing)*1)}@media (hover:hover){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-clickable:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-collapsed{display:none}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-selected{background-color:var(--gray-50)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-reorder-handle{margin-block:calc(var(--spacing)*2);margin-inline:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-checkbox{flex-shrink:0;margin-block:calc(var(--spacing)*4);margin-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn{display:flex;flex-direction:column;height:100%;padding-block:calc(var(--spacing)*4);row-gap:calc(var(--spacing)*3);width:100%}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn>:first-child{flex:1}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content{display:block;width:100%}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col{display:flex;justify-content:flex-start;text-align:start}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col:disabled{pointer-events:none}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-growable{width:100%}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-center{justify-content:center;text-align:center}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-end{justify-content:flex-end;text-align:end}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-left{justify-content:flex-start;text-align:left}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-right{justify-content:flex-end;text-align:right}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-between,.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-justify{justify-content:space-between;text-align:justify}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content.fi-collapsible{margin-top:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-growable{flex:1;width:100%}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .sm\:fi-hidden{display:none}}@media (min-width:48rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .md\:fi-hidden{display:none}}@media (min-width:64rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .lg\:fi-hidden{display:none}}@media (min-width:80rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .xl\:fi-hidden{display:none}}@media (min-width:96rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .\32 xl\:fi-hidden{display:none}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .sm\:fi-visible{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .sm\:fi-visible{display:block}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .md\:fi-visible{display:none}@media (min-width:48rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .md\:fi-visible{display:block}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .lg\:fi-visible{display:none}@media (min-width:64rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .lg\:fi-visible{display:block}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .xl\:fi-visible{display:none}@media (min-width:80rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .xl\:fi-visible{display:block}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .\32 xl\:fi-visible{display:none}@media (min-width:96rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .\32 xl\:fi-visible{display:block}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-collapse-btn{flex-shrink:0;margin-block:calc(var(--spacing)*2);margin-inline:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-actions.fi-ta-actions-before-columns-position{order:-9999}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record:not(.fi-ta-record-with-content-prefix) .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record:not(.fi-ta-record-with-content-prefix) .fi-ta-record-content{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record:not(.fi-ta-record-with-content-suffix) .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record:not(.fi-ta-record-with-content-suffix) .fi-ta-record-content{padding-inline-end:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-ta-record-collapsed .fi-ta-record-collapse-btn{rotate:180deg}.custom-fields-component .fi-ta-empty-state{padding-block:calc(var(--spacing)*12);padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-empty-state:not(.fi-ta-ctn-with-content-layout .fi-ta-empty-state){border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:1px}.custom-fields-component .fi-ta-empty-state:not(.fi-ta-ctn-with-content-layout .fi-ta-empty-state):where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-empty-state:not(.fi-ta-ctn-with-content-layout .fi-ta-empty-state):where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-content{display:grid;justify-items:center;margin-inline:auto;max-width:var(--container-lg);text-align:center}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-icon-bg{background-color:var(--gray-100);border-radius:3.40282e+38px;margin-bottom:calc(var(--spacing)*4);padding:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-icon-bg:where(.dark,.dark *){background-color:var(--gray-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-icon-bg:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-500)20%,transparent)}}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-icon-bg .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-icon-bg .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-description{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));margin-top:calc(var(--spacing)*1)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-empty-state .fi-ta-actions{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-header-cell{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*3.5);padding-inline:calc(var(--spacing)*3);text-align:start;--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}@media (min-width:40rem){.custom-fields-component .fi-ta-header-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-header-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-header-cell:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-header-cell.fi-growable{width:100%}.custom-fields-component .fi-ta-header-cell.fi-grouped{border-color:var(--gray-200)}.custom-fields-component .fi-ta-header-cell.fi-grouped:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-header-cell.fi-grouped:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-header-cell.fi-grouped:not(:first-of-type){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.custom-fields-component .fi-ta-header-cell.fi-grouped:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-ta-header-cell.fi-align-center{text-align:center}.custom-fields-component .fi-ta-header-cell.fi-align-center .fi-ta-header-cell-sort-btn{justify-content:center}.custom-fields-component .fi-ta-header-cell.fi-align-end{text-align:end}.custom-fields-component .fi-ta-header-cell.fi-align-end .fi-ta-header-cell-sort-btn{justify-content:flex-end}.custom-fields-component .fi-ta-header-cell.fi-align-left{text-align:left}.custom-fields-component .fi-ta-header-cell.fi-align-left .fi-ta-header-cell-sort-btn{justify-content:flex-start}.custom-fields-component .fi-ta-header-cell.fi-align-left .fi-ta-header-cell-sort-btn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){flex-direction:row-reverse}.custom-fields-component .fi-ta-header-cell.fi-align-right{text-align:right}.custom-fields-component .fi-ta-header-cell.fi-align-right .fi-ta-header-cell-sort-btn{justify-content:flex-end}.custom-fields-component .fi-ta-header-cell.fi-align-right .fi-ta-header-cell-sort-btn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){flex-direction:row-reverse}.custom-fields-component .fi-ta-header-cell.fi-align-between,.custom-fields-component .fi-ta-header-cell.fi-align-justify{text-align:justify}.custom-fields-component :is(.fi-ta-header-cell.fi-align-justify,.fi-ta-header-cell.fi-align-between) .fi-ta-header-cell-sort-btn{justify-content:space-between}.custom-fields-component .fi-ta-header-cell.fi-ta-header-cell-sorted .fi-icon{color:var(--gray-950)}.custom-fields-component .fi-ta-header-cell.fi-ta-header-cell-sorted .fi-icon:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-icon:where(.dark,.dark *),.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-ta-header-cell-sort-btn:hover .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-ta-header-cell-sort-btn:hover .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-ta-header-cell-sort-btn:focus-visible .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-ta-header-cell-sort-btn:focus-visible .fi-icon:where(.dark,.dark *){color:var(--gray-400)}@media (min-width:40rem){.custom-fields-component .fi-ta-header-cell.sm\:fi-hidden{display:none}}@media (min-width:48rem){.custom-fields-component .fi-ta-header-cell.md\:fi-hidden{display:none}}@media (min-width:64rem){.custom-fields-component .fi-ta-header-cell.lg\:fi-hidden{display:none}}@media (min-width:80rem){.custom-fields-component .fi-ta-header-cell.xl\:fi-hidden{display:none}}@media (min-width:96rem){.custom-fields-component .fi-ta-header-cell.\32 xl\:fi-hidden{display:none}}.custom-fields-component .fi-ta-header-cell.sm\:fi-visible{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-header-cell.sm\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-cell.md\:fi-visible{display:none}@media (min-width:48rem){.custom-fields-component .fi-ta-header-cell.md\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-cell.lg\:fi-visible{display:none}@media (min-width:64rem){.custom-fields-component .fi-ta-header-cell.lg\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-cell.xl\:fi-visible{display:none}@media (min-width:80rem){.custom-fields-component .fi-ta-header-cell.xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-cell.\32 xl\:fi-visible{display:none}@media (min-width:96rem){.custom-fields-component .fi-ta-header-cell.\32 xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-cell.fi-wrapped{white-space:normal}.custom-fields-component .fi-ta-header-cell:not(.fi-wrapped){white-space:nowrap}.custom-fields-component .fi-ta-header-cell .fi-ta-header-cell-sort-btn{align-items:center;column-gap:calc(var(--spacing)*1);cursor:pointer;display:flex;justify-content:flex-start;width:100%}.custom-fields-component .fi-ta-header-cell .fi-icon{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;flex-shrink:0;transition-duration:75ms}.custom-fields-component .fi-ta-header-group-cell{border-color:var(--gray-200);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}@media (min-width:40rem){.custom-fields-component .fi-ta-header-group-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-header-group-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-header-group-cell:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-header-group-cell:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-header-group-cell:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-header-group-cell:not(:first-of-type){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.custom-fields-component .fi-ta-header-group-cell:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-ta-header-group-cell.fi-align-start{text-align:start}.custom-fields-component .fi-ta-header-group-cell.fi-align-center{text-align:center}.custom-fields-component .fi-ta-header-group-cell.fi-align-end{text-align:end}.custom-fields-component .fi-ta-header-group-cell.fi-align-left{text-align:left}.custom-fields-component .fi-ta-header-group-cell.fi-align-right{text-align:right}.custom-fields-component .fi-ta-header-group-cell.fi-align-between,.custom-fields-component .fi-ta-header-group-cell.fi-align-justify{text-align:justify}@media (min-width:40rem){.custom-fields-component .fi-ta-header-group-cell.sm\:fi-hidden{display:none}}@media (min-width:48rem){.custom-fields-component .fi-ta-header-group-cell.md\:fi-hidden{display:none}}@media (min-width:64rem){.custom-fields-component .fi-ta-header-group-cell.lg\:fi-hidden{display:none}}@media (min-width:80rem){.custom-fields-component .fi-ta-header-group-cell.xl\:fi-hidden{display:none}}@media (min-width:96rem){.custom-fields-component .fi-ta-header-group-cell.\32 xl\:fi-hidden{display:none}}.custom-fields-component .fi-ta-header-group-cell.sm\:fi-visible{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-header-group-cell.sm\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-group-cell.md\:fi-visible{display:none}@media (min-width:48rem){.custom-fields-component .fi-ta-header-group-cell.md\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-group-cell.lg\:fi-visible{display:none}@media (min-width:64rem){.custom-fields-component .fi-ta-header-group-cell.lg\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-group-cell.xl\:fi-visible{display:none}@media (min-width:80rem){.custom-fields-component .fi-ta-header-group-cell.xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-group-cell.\32 xl\:fi-visible{display:none}@media (min-width:96rem){.custom-fields-component .fi-ta-header-group-cell.\32 xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-group-cell.fi-wrapped{white-space:normal}.custom-fields-component .fi-ta-header-group-cell:not(.fi-wrapped){white-space:nowrap}.custom-fields-component .fi-ta-empty-header-cell{width:calc(var(--spacing)*1)}@media (hover:hover){.custom-fields-component .fi-ta-row{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-ta-row.fi-clickable:hover{background-color:var(--gray-50)}.custom-fields-component .fi-ta-row.fi-clickable:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-row.fi-clickable:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-ta-row.fi-striped{background-color:var(--gray-50)}.custom-fields-component .fi-ta-row.fi-striped:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-row.fi-striped:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-row.fi-collapsed{display:none}.custom-fields-component .fi-ta-row.fi-ta-group-header-row>td{background-color:var(--gray-50)}.custom-fields-component .fi-ta-row.fi-ta-group-header-row>td:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-row.fi-ta-group-header-row>td:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-row .fi-ta-group-header-cell{padding-inline:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-ta-row .fi-ta-group-header-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-row .fi-ta-group-header-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-row .fi-ta-group-header{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;padding-block:calc(var(--spacing)*2);width:100%}.custom-fields-component .fi-ta-row .fi-ta-group-header.fi-collapsible{cursor:pointer}.custom-fields-component .fi-ta-row .fi-ta-group-header.fi-collapsible.fi-collapsed .fi-icon-btn{rotate:-180deg}.custom-fields-component .fi-ta-row .fi-ta-group-header .fi-ta-group-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-row .fi-ta-group-header .fi-ta-group-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-row .fi-ta-group-header .fi-ta-group-description{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-ta-row .fi-ta-group-header .fi-ta-group-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-row.fi-selected:not(.fi-striped){background-color:var(--gray-50)}.custom-fields-component .fi-ta-row.fi-selected:not(.fi-striped):where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-row.fi-selected:not(.fi-striped):where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-row.fi-selected>:first-child{position:relative}.custom-fields-component .fi-ta-row.fi-selected>:first-child:before{background-color:var(--primary-600);content:"";inset-block:calc(var(--spacing)*0);inset-inline-start:calc(var(--spacing)*0);position:absolute;width:calc(var(--spacing)*.5)}.custom-fields-component .fi-ta-row.fi-selected>:first-child:where(.dark,.dark *):before{background-color:var(--primary-500)}.custom-fields-component .fi-ta-reordering .fi-ta-row:not(.fi-ta-row-not-reorderable){cursor:move}.custom-fields-component .fi-ta-table{table-layout:auto;width:100%}.custom-fields-component :where(.fi-ta-table>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-ta-table{text-align:start}.custom-fields-component :where(.fi-ta-table:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-ta-table:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component :where(.fi-ta-table>thead>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-ta-table>thead:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-ta-table>thead:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-table>thead>tr{background-color:var(--gray-50)}.custom-fields-component .fi-ta-table>thead>tr:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-table>thead>tr:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-table>thead>tr.fi-ta-table-head-groups-row{background-color:var(--gray-100)}.custom-fields-component .fi-ta-table>thead>tr.fi-ta-table-head-groups-row:where(.dark,.dark *){background-color:#0000}.custom-fields-component :where(.fi-ta-table>tbody>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component .fi-ta-table>tbody{white-space:nowrap}.custom-fields-component :where(.fi-ta-table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-ta-table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-table>tfoot{background-color:var(--gray-50)}.custom-fields-component .fi-ta-table>tfoot:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-table>tfoot:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-ta-col-manager{display:grid;row-gap:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-ctn{display:grid;gap:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-header{align-items:center;display:flex;justify-content:space-between}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-items{column-gap:calc(var(--spacing)*6);margin-top:calc(var(--spacing)*-6)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-item{align-items:center;break-inside:avoid;display:flex;gap:calc(var(--spacing)*3);padding-top:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-item .fi-ta-col-manager-label{align-items:center;column-gap:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));width:100%;--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);display:flex;flex:1;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-item .fi-ta-col-manager-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-item .fi-ta-col-manager-label .fi-checkbox-input{flex-shrink:0}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-item .fi-ta-col-manager-reorder-handle{cursor:move}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-group{break-inside:avoid}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-group .fi-ta-col-manager-group-items{padding-inline-start:calc(var(--spacing)*8)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-actions-ctn{display:flex;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-wi-chart .fi-wi-chart-canvas-ctn{margin-inline:auto}.custom-fields-component .fi-wi-chart .fi-wi-chart-canvas-ctn:not(.fi-wi-chart-canvas-ctn-no-aspect-ratio){aspect-ratio:1}@supports (container-type:inline-size){.custom-fields-component .fi-wi-chart .fi-section-content{container-type:inline-size}@container (min-width:24rem){.custom-fields-component .fi-wi-chart .fi-wi-chart-canvas-ctn:not(.fi-wi-chart-canvas-ctn-no-aspect-ratio){aspect-ratio:1.5}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-wi-chart .fi-wi-chart-canvas-ctn:not(.fi-wi-chart-canvas-ctn-no-aspect-ratio){aspect-ratio:1.5}}}.custom-fields-component .fi-wi-chart .fi-wi-chart-filter.fi-input-wrp{width:max-content}@media (min-width:40rem){.custom-fields-component .fi-wi-chart .fi-wi-chart-filter.fi-input-wrp{margin-block:calc(var(--spacing)*-2)}}.custom-fields-component .fi-wi-chart .fi-wi-chart-filter.fi-dropdown .fi-wi-chart-filter-content{padding:calc(var(--spacing)*6)}.custom-fields-component .fi-wi-chart .fi-color .fi-wi-chart-bg-color{color:var(--color-50)}.custom-fields-component .fi-wi-chart .fi-color .fi-wi-chart-bg-color:where(.dark,.dark *){color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-wi-chart .fi-color .fi-wi-chart-bg-color:where(.dark,.dark *){color:color-mix(in oklab,var(--color-400)10%,transparent)}}.custom-fields-component .fi-wi-chart .fi-color .fi-wi-chart-border-color{color:var(--color-500)}.custom-fields-component .fi-wi-chart .fi-color .fi-wi-chart-border-color:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-wi-chart .fi-wi-chart-bg-color{color:var(--gray-100)}.custom-fields-component .fi-wi-chart .fi-wi-chart-bg-color:where(.dark,.dark *){color:var(--gray-800)}.custom-fields-component .fi-wi-chart .fi-wi-chart-border-color{color:var(--gray-400)}.custom-fields-component .fi-wi-chart .fi-wi-chart-grid-color{color:var(--gray-200)}.custom-fields-component .fi-wi-chart .fi-wi-chart-grid-color:where(.dark,.dark *){color:var(--gray-800)}.custom-fields-component .fi-wi-chart .fi-wi-chart-text-color{color:var(--gray-500)}.custom-fields-component .fi-wi-chart .fi-wi-chart-text-color:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-wi-stats-overview-stat{background-color:var(--color-white);border-radius:var(--radius-xl);height:100%;padding:calc(var(--spacing)*6);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent);display:block;position:relative}.custom-fields-component .fi-wi-stats-overview-stat:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-wi-stats-overview-stat:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-wi-stats-overview-stat .fi-icon{color:var(--gray-400);flex-shrink:0}.custom-fields-component .fi-wi-stats-overview-stat .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-content{display:grid;row-gap:calc(var(--spacing)*2)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-label-ctn{align-items:center;column-gap:calc(var(--spacing)*2);display:flex}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-500);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-value{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);color:var(--gray-950);letter-spacing:var(--tracking-tight)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-value:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-description{align-items:center;color:var(--gray-500);column-gap:calc(var(--spacing)*1);display:flex;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-description.fi-color{color:var(--text)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-description.fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-description.fi-color .fi-icon{color:var(--color-500)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart{border-bottom-left-radius:var(--radius-xl);border-bottom-right-radius:var(--radius-xl);inset-inline:calc(var(--spacing)*0);bottom:calc(var(--spacing)*0);overflow:hidden;position:absolute}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart>canvas{height:calc(var(--spacing)*6)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart .fi-wi-stats-overview-stat-chart-bg-color{color:var(--gray-100)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart .fi-wi-stats-overview-stat-chart-bg-color:where(.dark,.dark *){color:var(--gray-800)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart .fi-wi-stats-overview-stat-chart-border-color{color:var(--gray-400)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart.fi-color .fi-wi-stats-overview-stat-chart-bg-color{color:var(--color-50)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart.fi-color .fi-wi-stats-overview-stat-chart-bg-color:where(.dark,.dark *){color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart.fi-color .fi-wi-stats-overview-stat-chart-bg-color:where(.dark,.dark *){color:color-mix(in oklab,var(--color-400)10%,transparent)}}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart.fi-color .fi-wi-stats-overview-stat-chart-border-color{color:var(--color-500)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart.fi-color .fi-wi-stats-overview-stat-chart-border-color:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-wi{gap:calc(var(--spacing)*6)}.custom-fields-component .fi-global-search-ctn{align-items:center;display:flex}.custom-fields-component .fi-global-search{flex:1}@media (min-width:40rem){.custom-fields-component .fi-global-search{position:relative}}.custom-fields-component .fi-global-search-results-ctn{background-color:var(--color-white);border-radius:var(--radius-lg);inset-inline:calc(var(--spacing)*4);margin-top:calc(var(--spacing)*2);max-height:calc(var(--spacing)*96);z-index:10;--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent);overflow:auto;position:absolute;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}@media (min-width:40rem){.custom-fields-component .fi-global-search-results-ctn{inset-inline:auto}}.custom-fields-component .fi-global-search-results-ctn:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-global-search-results-ctn:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-global-search-results-ctn{transform:translateZ(0)}.custom-fields-component .fi-global-search-results-ctn.fi-transition-enter-start,.custom-fields-component .fi-global-search-results-ctn.fi-transition-leave-end{opacity:0}@media (min-width:40rem){.custom-fields-component .fi-topbar .fi-global-search-results-ctn{inset-inline-end:calc(var(--spacing)*0);max-width:var(--container-sm);width:100vw}}.custom-fields-component .fi-sidebar .fi-global-search-ctn{margin-inline:calc(var(--spacing)*3);margin-top:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-sidebar .fi-global-search-results-ctn{inset-inline-start:calc(var(--spacing)*0)}}.custom-fields-component .fi-global-search-no-results-message{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*4);padding-inline:calc(var(--spacing)*4)}.custom-fields-component .fi-global-search-no-results-message:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component :where(.fi-global-search-results>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-global-search-results:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-global-search-results:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-global-search-result-group-header{background-color:var(--gray-50);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*4);top:calc(var(--spacing)*0);z-index:10;--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);font-weight:var(--font-weight-semibold);position:sticky;text-transform:capitalize}.custom-fields-component .fi-global-search-result-group-header:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-global-search-result-group-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-global-search-result-group-header:where(.dark,.dark *){background-color:var(--gray-800);color:var(--color-white)}.custom-fields-component :where(.fi-global-search-result-group-results>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.fi-global-search-result-group-results:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-global-search-result-group-results:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-global-search-result{scroll-margin-top:calc(var(--spacing)*9);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-global-search-result:focus-within{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-global-search-result:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-global-search-result:where(.dark,.dark *):focus-within{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-global-search-result:where(.dark,.dark *):focus-within{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}@media (hover:hover){.custom-fields-component .fi-global-search-result:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-global-search-result:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-global-search-result.fi-global-search-result-has-actions .fi-global-search-result-link{padding-bottom:calc(var(--spacing)*0)}.custom-fields-component .fi-global-search-result-link{padding:calc(var(--spacing)*4);--tw-outline-style:none;display:block;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-global-search-result-link{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-global-search-result-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-950);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-global-search-result-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-global-search-result-details{margin-top:calc(var(--spacing)*1)}.custom-fields-component .fi-global-search-result-detail{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-global-search-result-detail:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-global-search-result-detail-label{--tw-font-weight:var(--font-weight-medium);display:inline;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-global-search-result-detail-value{display:inline}.custom-fields-component .fi-global-search-result-actions{column-gap:calc(var(--spacing)*3);display:flex;margin-top:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4);padding-bottom:calc(var(--spacing)*4)}.custom-fields-component .fi-header{display:flex;flex-direction:column;gap:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-header{align-items:center;flex-direction:row;justify-content:space-between}}.custom-fields-component .fi-header .fi-breadcrumbs{display:none;margin-bottom:calc(var(--spacing)*2)}@media (min-width:40rem){.custom-fields-component .fi-header .fi-breadcrumbs{display:block}.custom-fields-component .fi-header.fi-header-has-breadcrumbs .fi-header-actions-ctn{margin-top:calc(var(--spacing)*7)}}.custom-fields-component .fi-header-heading{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-tight);color:var(--gray-950);letter-spacing:var(--tracking-tight)}@media (min-width:40rem){.custom-fields-component .fi-header-heading{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}}.custom-fields-component .fi-header-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-header-subheading{color:var(--gray-600);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));margin-top:calc(var(--spacing)*2);max-width:var(--container-2xl)}.custom-fields-component .fi-header-subheading:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-header-actions-ctn{align-items:center;display:flex;flex-shrink:0;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-header-actions-ctn>.fi-ac{flex:1}.custom-fields-component .fi-header-actions-ctn>.fi-ac:not(.fi-width-full).fi-align-end,.custom-fields-component .fi-header-actions-ctn>.fi-ac:not(.fi-width-full).fi-align-right{flex-direction:row;justify-content:flex-end}.custom-fields-component .fi-simple-header{align-items:center;display:flex;flex-direction:column}.custom-fields-component .fi-simple-header .fi-logo{margin-bottom:calc(var(--spacing)*4)}.custom-fields-component .fi-simple-header-heading{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));text-align:center;--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-tight);color:var(--gray-950);letter-spacing:var(--tracking-tight)}.custom-fields-component .fi-simple-header-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-simple-header-subheading{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));margin-top:calc(var(--spacing)*2);text-align:center}.custom-fields-component .fi-simple-header-subheading:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component html.fi{min-height:100dvh}.custom-fields-component .fi-body{background-color:var(--gray-50);--tw-font-weight:var(--font-weight-normal);color:var(--gray-950);font-weight:var(--font-weight-normal);min-height:100dvh;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.custom-fields-component .fi-body:where(.dark,.dark *){background-color:var(--gray-950);color:var(--color-white)}.custom-fields-component :is(.fi-body.fi-body-has-sidebar-collapsible-on-desktop,.fi-body.fi-body-has-sidebar-fully-collapsible-on-desktop) .fi-main-ctn{min-height:calc(100dvh - 4rem);opacity:0;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.custom-fields-component .fi-body>.fi-layout-sidebar-toggle-btn-ctn{padding-inline-start:calc(var(--spacing)*5);padding-top:calc(var(--spacing)*5)}@media (min-width:64rem){.custom-fields-component .fi-body>.fi-layout-sidebar-toggle-btn-ctn.lg\:fi-hidden{display:none}}.custom-fields-component .fi-body.fi-body-has-navigation:not(.fi-body-has-sidebar-collapsible-on-desktop):not(.fi-body-has-sidebar-fully-collapsible-on-desktop):not(.fi-body-has-top-navigation) .fi-main-ctn{opacity:0}.custom-fields-component :is(.fi-body.fi-body-has-top-navigation,.fi-body:not(.fi-body-has-navigation)) .fi-main-ctn{display:flex;min-height:calc(100dvh - 4rem)}.custom-fields-component .fi-body:not(.fi-body-has-topbar) .fi-main-ctn{display:flex;min-height:100dvh}.custom-fields-component .fi-layout{display:flex;height:100%;overflow-x:clip;width:100%}.custom-fields-component .fi-main-ctn{flex:1;flex-direction:column;width:100vw}.custom-fields-component .fi-main{height:100%;margin-inline:auto;padding-inline:calc(var(--spacing)*4);width:100%}@media (min-width:48rem){.custom-fields-component .fi-main{padding-inline:calc(var(--spacing)*6)}}@media (min-width:64rem){.custom-fields-component .fi-main{padding-inline:calc(var(--spacing)*8)}}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-xs{max-width:var(--container-xs)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-sm{max-width:var(--container-sm)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-md{max-width:var(--container-md)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-lg{max-width:var(--container-lg)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-xl{max-width:var(--container-xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-2xl{max-width:var(--container-2xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-3xl{max-width:var(--container-3xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-4xl{max-width:var(--container-4xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-5xl{max-width:var(--container-5xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-6xl{max-width:var(--container-6xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-7xl{max-width:var(--container-7xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-full{max-width:100%}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-min{max-width:min-content}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-max{max-width:max-content}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-fit{max-width:fit-content}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-prose{max-width:65ch}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen-sm{max-width:var(--breakpoint-sm)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen-md{max-width:var(--breakpoint-md)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen-lg{max-width:var(--breakpoint-lg)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen-xl{max-width:var(--breakpoint-xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen-2xl{max-width:var(--breakpoint-2xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen{inset:calc(var(--spacing)*0);position:fixed}.custom-fields-component .fi-simple-layout{align-items:center;display:flex;flex-direction:column;min-height:100dvh}.custom-fields-component .fi-simple-layout-header{align-items:center;column-gap:calc(var(--spacing)*4);display:flex;height:calc(var(--spacing)*16);inset-inline-end:calc(var(--spacing)*0);padding-inline-end:calc(var(--spacing)*4);position:absolute;top:calc(var(--spacing)*0)}@media (min-width:48rem){.custom-fields-component .fi-simple-layout-header{padding-inline-end:calc(var(--spacing)*6)}}@media (min-width:64rem){.custom-fields-component .fi-simple-layout-header{padding-inline-end:calc(var(--spacing)*8)}}.custom-fields-component .fi-simple-main-ctn{align-items:center;display:flex;flex-grow:1;justify-content:center;width:100%}.custom-fields-component .fi-simple-main{background-color:var(--color-white);margin-block:calc(var(--spacing)*16);padding-block:calc(var(--spacing)*12);padding-inline:calc(var(--spacing)*6);width:100%;--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}@media (min-width:40rem){.custom-fields-component .fi-simple-main{border-radius:var(--radius-xl);padding-inline:calc(var(--spacing)*12)}}.custom-fields-component .fi-simple-main:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-simple-main:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-logo{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5);--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-tight);color:var(--gray-950);display:flex;letter-spacing:var(--tracking-tight)}.custom-fields-component .fi-logo:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-logo.fi-logo-dark,.custom-fields-component .fi-logo.fi-logo-light:where(.dark,.dark *){display:none}.custom-fields-component .fi-logo.fi-logo-dark:where(.dark,.dark *){display:flex}@media (min-width:48rem){.custom-fields-component .fi-page-sub-navigation-dropdown{display:none}}.custom-fields-component .fi-page-sub-navigation-dropdown>.fi-dropdown-trigger>.fi-btn{justify-content:space-between;width:100%}.custom-fields-component .fi-page-sub-navigation-sidebar-ctn{display:none;flex-direction:column;width:calc(var(--spacing)*72)}@media (min-width:48rem){.custom-fields-component .fi-page-sub-navigation-sidebar-ctn{display:flex}}.custom-fields-component .fi-page-sub-navigation-sidebar{display:flex;flex-direction:column;row-gap:calc(var(--spacing)*7)}.custom-fields-component .fi-page-sub-navigation-tabs{display:none}@media (min-width:48rem){.custom-fields-component .fi-page-sub-navigation-tabs{display:flex}}.custom-fields-component .fi-page.fi-height-full,.custom-fields-component .fi-page.fi-height-full .fi-page-content,.custom-fields-component .fi-page.fi-height-full .fi-page-header-main-ctn,.custom-fields-component .fi-page.fi-height-full .fi-page-main{height:100%}.custom-fields-component .fi-page.fi-page-has-sub-navigation .fi-page-main{display:flex;flex-direction:column;gap:calc(var(--spacing)*8)}@media (min-width:48rem){.custom-fields-component :is(.fi-page.fi-page-has-sub-navigation.fi-page-has-sub-navigation-start,.fi-page.fi-page-has-sub-navigation.fi-page-has-sub-navigation-end) .fi-page-main{align-items:flex-start;flex-direction:row}}.custom-fields-component .fi-page-header-main-ctn{display:flex;flex-direction:column;padding-block:calc(var(--spacing)*8);row-gap:calc(var(--spacing)*8)}.custom-fields-component .fi-page-main-sub-navigation-mobile-menu-render-hook-ctn{display:contents}@media (min-width:48rem){.custom-fields-component .fi-page-main-sub-navigation-mobile-menu-render-hook-ctn{display:none}}.custom-fields-component .fi-page-content{display:grid;flex:1;grid-auto-columns:minmax(0,1fr);row-gap:calc(var(--spacing)*8)}.custom-fields-component .fi-simple-page-content{display:grid;grid-auto-columns:minmax(0,1fr);row-gap:calc(var(--spacing)*6)}.custom-fields-component .fi-sidebar-group{display:flex;flex-direction:column;row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-sidebar-group.fi-collapsed .fi-sidebar-group-collapse-btn{rotate:-180deg}.custom-fields-component .fi-sidebar-group.fi-collapsible>.fi-sidebar-group-btn{cursor:pointer}.custom-fields-component .fi-sidebar-group.fi-active .fi-sidebar-group-dropdown-trigger-btn .fi-icon{color:var(--primary-600)}.custom-fields-component .fi-sidebar-group.fi-active .fi-sidebar-group-dropdown-trigger-btn .fi-icon:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-sidebar-group-btn{align-items:center;column-gap:calc(var(--spacing)*3);display:flex;padding:calc(var(--spacing)*2)}.custom-fields-component .fi-sidebar-group-btn .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-sidebar-group-btn .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-sidebar-group-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);color:var(--gray-500);flex:1;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-sidebar-group-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn{align-items:center;border-radius:var(--radius-lg);column-gap:calc(var(--spacing)*3);justify-content:center;padding:calc(var(--spacing)*2);--tw-outline-style:none;display:flex;flex:1;outline-style:none;position:relative}@media (forced-colors:active){.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:hover{background-color:var(--gray-100)}}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:focus-visible{background-color:var(--gray-100)}@media (hover:hover){.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-sidebar-group-items{display:flex;flex-direction:column;row-gap:calc(var(--spacing)*1)}.custom-fields-component :is(.fi-sidebar-group-btn,.fi-sidebar-group-items).fi-transition-enter{transition-delay:.1s}@media (min-width:64rem){.custom-fields-component :is(.fi-sidebar-group-btn,.fi-sidebar-group-items).fi-transition-enter{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}}.custom-fields-component :is(.fi-sidebar-group-btn,.fi-sidebar-group-items).fi-transition-enter-start{opacity:0}.custom-fields-component :is(.fi-sidebar-group-btn,.fi-sidebar-group-items).fi-transition-enter-end{opacity:1}.custom-fields-component .fi-sidebar{align-content:flex-start;background-color:var(--color-white);display:flex;flex-direction:column;height:100dvh;inset-block:calc(var(--spacing)*0);inset-inline-start:calc(var(--spacing)*0);position:fixed;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));z-index:30}@media (min-width:64rem){.custom-fields-component .fi-sidebar{background-color:#0000;transition-property:none;z-index:20}}.custom-fields-component .fi-sidebar:where(.dark,.dark *){background-color:var(--gray-900)}@media (min-width:64rem){.custom-fields-component .fi-sidebar:where(.dark,.dark *){background-color:#0000}}.custom-fields-component .fi-sidebar.fi-sidebar-open{width:var(--sidebar-width);--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}@media (min-width:64rem){.custom-fields-component .fi-sidebar.fi-sidebar-open{--tw-shadow:0 0 #0000;--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.custom-fields-component .fi-sidebar.fi-sidebar-open:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-sidebar.fi-sidebar-open:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar.fi-sidebar-open:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-sidebar:not(.fi-sidebar-open){--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-sidebar:not(.fi-sidebar-open):where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}@media (min-width:64rem){.custom-fields-component .fi-body-has-topbar .fi-sidebar{height:calc(100dvh - 4rem);top:4rem}}.custom-fields-component .fi-sidebar-close-overlay{background-color:var(--gray-950);inset:calc(var(--spacing)*0);position:fixed;z-index:30}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-close-overlay{background-color:color-mix(in oklab,var(--gray-950)50%,transparent)}}.custom-fields-component .fi-sidebar-close-overlay{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:.5s;transition-duration:.5s}@media (min-width:64rem){.custom-fields-component .fi-sidebar-close-overlay{display:none}}.custom-fields-component .fi-sidebar-close-overlay:where(.dark,.dark *){background-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-close-overlay:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-950)75%,transparent)}}@media (min-width:64rem){.custom-fields-component .fi-body.fi-body-has-top-navigation .fi-sidebar{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-body.fi-body-has-top-navigation .fi-sidebar:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .fi-body:not(.fi-body-has-top-navigation) .fi-sidebar.fi-sidebar-open,.custom-fields-component .fi-body:not(.fi-body-has-top-navigation).fi-body-has-sidebar-collapsible-on-desktop .fi-sidebar:not(.fi-sidebar-open){position:sticky}.custom-fields-component .fi-body:not(.fi-body-has-top-navigation).fi-body-has-sidebar-collapsible-on-desktop .fi-sidebar:not(.fi-sidebar-open),.custom-fields-component .fi-body:not(.fi-body-has-top-navigation).fi-body-has-sidebar-collapsible-on-desktop .fi-sidebar:not(.fi-sidebar-open):where(:dir(rtl),[dir=rtl],[dir=rtl] *),.custom-fields-component .fi-body:not(.fi-body-has-top-navigation):not(.fi-body-has-sidebar-collapsible-on-desktop):not(.fi-body-has-sidebar-fully-collapsible-on-desktop) .fi-sidebar,.custom-fields-component .fi-body:not(.fi-body-has-top-navigation):not(.fi-body-has-sidebar-collapsible-on-desktop):not(.fi-body-has-sidebar-fully-collapsible-on-desktop) .fi-sidebar:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}}.custom-fields-component .fi-body:not(.fi-body-has-top-navigation):not(.fi-body-has-sidebar-collapsible-on-desktop):not(.fi-body-has-sidebar-fully-collapsible-on-desktop) .fi-sidebar:not(.fi-sidebar-open){width:var(--sidebar-width)}@media (min-width:64rem){.custom-fields-component .fi-body:not(.fi-body-has-top-navigation):not(.fi-body-has-sidebar-collapsible-on-desktop):not(.fi-body-has-sidebar-fully-collapsible-on-desktop) .fi-sidebar:not(.fi-sidebar-open){position:sticky}}.custom-fields-component .fi-sidebar-header-ctn{overflow-x:clip}.custom-fields-component .fi-sidebar-header{align-items:center;display:flex;height:calc(var(--spacing)*16);justify-content:center}.custom-fields-component .fi-sidebar-header-logo-ctn{flex:1}.custom-fields-component .fi-body-has-topbar .fi-sidebar-header{background-color:var(--color-white);padding-inline:calc(var(--spacing)*6);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-body-has-topbar .fi-sidebar-header{--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}}@media (min-width:64rem){.custom-fields-component .fi-body-has-topbar .fi-sidebar-header{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);display:none}}.custom-fields-component .fi-body-has-topbar .fi-sidebar-header:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-body-has-topbar .fi-sidebar-header:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component :not(.fi-body-has-topbar) .fi-sidebar-header{padding-inline:calc(var(--spacing)*4);--tw-shadow:0 0 #0000;--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);background-color:#0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component :not(.fi-body-has-topbar) .fi-sidebar-header .fi-logo{margin-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-sidebar-nav{display:flex;flex-direction:column;flex-grow:1;overflow:hidden auto;padding-block:calc(var(--spacing)*8);padding-inline:calc(var(--spacing)*6);row-gap:calc(var(--spacing)*7);scrollbar-gutter:stable}.custom-fields-component .fi-sidebar-nav-groups{display:flex;flex-direction:column;margin-inline:calc(var(--spacing)*-2);row-gap:calc(var(--spacing)*7)}.custom-fields-component .fi-sidebar-item.fi-active,.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-active-child-items{display:flex;flex-direction:column;row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn{background-color:var(--gray-100)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-icon{color:var(--primary-700)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-icon:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-sidebar-item-grouped-border>.fi-sidebar-item-grouped-border-part{background-color:var(--primary-700)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-sidebar-item-grouped-border>.fi-sidebar-item-grouped-border-part:where(.dark,.dark *){background-color:var(--primary-400)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-sidebar-item-label{color:var(--primary-700)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-sidebar-item-label:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn .fi-sidebar-item-grouped-border-part{border-radius:3.40282e+38px;height:calc(var(--spacing)*1.5);position:relative;width:calc(var(--spacing)*1.5)}@media (hover:hover){.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:hover{background-color:var(--gray-100)}}.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:focus-visible{background-color:var(--gray-100)}@media (hover:hover){.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-sidebar-item-btn{align-items:center;border-radius:var(--radius-lg);column-gap:calc(var(--spacing)*3);justify-content:center;padding:calc(var(--spacing)*2);--tw-outline-style:none;display:flex;outline-style:none;position:relative}@media (forced-colors:active){.custom-fields-component .fi-sidebar-item-btn{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-sidebar-item-btn{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-sidebar-item-btn>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-sidebar-item-btn>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-sidebar-item-grouped-border{align-items:center;display:flex;height:calc(var(--spacing)*6);justify-content:center;position:relative;width:calc(var(--spacing)*6)}.custom-fields-component .fi-sidebar-item-grouped-border-part-not-first{background-color:var(--gray-300);bottom:50%;position:absolute;top:-50%;width:1px}.custom-fields-component .fi-sidebar-item-grouped-border-part-not-first:where(.dark,.dark *){background-color:var(--gray-600)}.custom-fields-component .fi-sidebar-item-grouped-border-part-not-last{background-color:var(--gray-300);bottom:-50%;position:absolute;top:50%;width:1px}.custom-fields-component .fi-sidebar-item-grouped-border-part-not-last:where(.dark,.dark *){background-color:var(--gray-600)}.custom-fields-component .fi-sidebar-item-grouped-border-part{background-color:var(--gray-400);border-radius:3.40282e+38px;height:calc(var(--spacing)*1.5);position:relative;width:calc(var(--spacing)*1.5)}.custom-fields-component .fi-sidebar-item-grouped-border-part:where(.dark,.dark *){background-color:var(--gray-500)}.custom-fields-component .fi-sidebar-item-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));text-overflow:ellipsis;white-space:nowrap;--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);flex:1;font-weight:var(--font-weight-medium);overflow:hidden}.custom-fields-component .fi-sidebar-item-label:where(.dark,.dark *){color:var(--gray-200)}@media (min-width:64rem){.custom-fields-component :is(.fi-sidebar-item-label,.fi-sidebar-item-badge-ctn).fi-transition-enter{transition-delay:.1s;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}}.custom-fields-component :is(.fi-sidebar-item-label,.fi-sidebar-item-badge-ctn).fi-transition-enter-start{opacity:0}.custom-fields-component :is(.fi-sidebar-item-label,.fi-sidebar-item-badge-ctn).fi-transition-enter-end{opacity:1}.custom-fields-component .fi-sidebar-footer{display:grid;margin-block:calc(var(--spacing)*3);margin-inline:calc(var(--spacing)*4);row-gap:calc(var(--spacing)*3)}.custom-fields-component .fi-sidebar-footer>.fi-no-database{display:block}.custom-fields-component .fi-sidebar-sub-group-items{display:flex;flex-direction:column;row-gap:calc(var(--spacing)*1)}.custom-fields-component .fi-sidebar-database-notifications-btn{align-items:center;border-radius:var(--radius-lg);column-gap:calc(var(--spacing)*3);justify-content:center;padding:calc(var(--spacing)*2);text-align:start;width:100%;--tw-outline-style:none;display:flex;outline-style:none;position:relative}@media (forced-colors:active){.custom-fields-component .fi-sidebar-database-notifications-btn{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-sidebar-database-notifications-btn{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-sidebar-database-notifications-btn:hover{background-color:var(--gray-100)}}.custom-fields-component .fi-sidebar-database-notifications-btn:focus-visible{background-color:var(--gray-100)}@media (hover:hover){.custom-fields-component .fi-sidebar-database-notifications-btn:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-database-notifications-btn:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-sidebar-database-notifications-btn:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-database-notifications-btn:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-sidebar-database-notifications-btn>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-sidebar-database-notifications-btn>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));text-overflow:ellipsis;white-space:nowrap;--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);flex:1;font-weight:var(--font-weight-medium);overflow:hidden}.custom-fields-component .fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-label:where(.dark,.dark *){color:var(--gray-200)}@media (min-width:64rem){.custom-fields-component :is(.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-label,.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-badge-ctn).fi-transition-enter{transition-delay:.1s;transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}}.custom-fields-component :is(.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-label,.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-badge-ctn).fi-transition-enter-start{opacity:0}.custom-fields-component :is(.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-label,.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-badge-ctn).fi-transition-enter-end{opacity:1}.custom-fields-component .fi-sidebar-open-collapse-sidebar-btn,.custom-fields-component .fi-sidebar-open-sidebar-btn{margin-inline:calc(var(--spacing)*0)!important}.custom-fields-component .fi-sidebar-close-collapse-sidebar-btn{display:none;margin-inline:calc(var(--spacing)*0)!important}@media (min-width:64rem){.custom-fields-component .fi-sidebar-close-collapse-sidebar-btn{display:flex}.custom-fields-component :is(.fi-body.fi-body-has-sidebar-collapsible-on-desktop,.fi-body:not(.fi-body-has-sidebar-fully-collapsible-on-desktop)) .fi-sidebar-open-sidebar-btn{display:none}}.custom-fields-component .fi-sidebar-close-sidebar-btn{margin-inline:calc(var(--spacing)*0)!important}@media (min-width:64rem){.custom-fields-component .fi-sidebar-close-sidebar-btn{display:none}}.custom-fields-component .fi-tenant-menu-trigger{align-items:center;border-radius:var(--radius-lg);column-gap:calc(var(--spacing)*3);font-size:var(--text-sm);justify-content:center;line-height:var(--tw-leading,var(--text-sm--line-height));padding:calc(var(--spacing)*2);width:100%;--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-outline-style:none;display:flex;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-tenant-menu-trigger{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-tenant-menu-trigger{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-tenant-menu-trigger:hover{background-color:var(--gray-100)}}.custom-fields-component .fi-tenant-menu-trigger:focus-visible{background-color:var(--gray-100)}@media (hover:hover){.custom-fields-component .fi-tenant-menu-trigger:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tenant-menu-trigger:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-tenant-menu-trigger:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tenant-menu-trigger:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-tenant-menu-trigger .fi-tenant-avatar{flex-shrink:0}.custom-fields-component .fi-tenant-menu-trigger .fi-icon{color:var(--gray-400);height:calc(var(--spacing)*5);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:calc(var(--spacing)*5);--tw-duration:75ms;flex-shrink:0;margin-inline-start:auto;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-tenant-menu-trigger .fi-icon:is(:where(.group):hover *){color:var(--gray-500)}}.custom-fields-component .fi-tenant-menu-trigger .fi-icon:is(:where(.group):focus-visible *),.custom-fields-component .fi-tenant-menu-trigger .fi-icon:where(.dark,.dark *){color:var(--gray-500)}@media (hover:hover){.custom-fields-component .fi-tenant-menu-trigger .fi-icon:where(.dark,.dark *):is(:where(.group):hover *){color:var(--gray-400)}}.custom-fields-component .fi-tenant-menu-trigger .fi-icon:where(.dark,.dark *):is(:where(.group):focus-visible *){color:var(--gray-400)}.custom-fields-component .fi-tenant-menu-trigger:hover .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-tenant-menu-trigger:hover .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-tenant-menu-trigger:focus-visible .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-tenant-menu-trigger:focus-visible .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-tenant-menu-trigger-text{display:grid;justify-items:start;text-align:start}.custom-fields-component .fi-tenant-menu-trigger-current-tenant-label{color:var(--gray-500);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-tenant-menu-trigger-current-tenant-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-tenant-menu-trigger-tenant-name{color:var(--gray-950)}.custom-fields-component .fi-tenant-menu-trigger-tenant-name:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sidebar .fi-tenant-menu{margin-inline:calc(var(--spacing)*4);margin-top:calc(var(--spacing)*3)}.custom-fields-component .fi-theme-switcher{column-gap:calc(var(--spacing)*1);display:grid;grid-auto-flow:column}.custom-fields-component .fi-theme-switcher-btn{border-radius:var(--radius-md);padding:calc(var(--spacing)*2);--tw-outline-style:none;display:flex;justify-content:center;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-theme-switcher-btn{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-theme-switcher-btn{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-theme-switcher-btn:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-theme-switcher-btn:focus-visible{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-theme-switcher-btn:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-theme-switcher-btn:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-theme-switcher-btn:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-theme-switcher-btn:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-theme-switcher-btn.fi-active{background-color:var(--gray-50);color:var(--primary-500)}.custom-fields-component .fi-theme-switcher-btn.fi-active:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-theme-switcher-btn.fi-active:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-theme-switcher-btn.fi-active:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-theme-switcher-btn:not(.fi-active){color:var(--gray-400)}@media (hover:hover){.custom-fields-component .fi-theme-switcher-btn:not(.fi-active):hover{color:var(--gray-500)}}.custom-fields-component .fi-theme-switcher-btn:not(.fi-active):focus-visible,.custom-fields-component .fi-theme-switcher-btn:not(.fi-active):where(.dark,.dark *){color:var(--gray-500)}@media (hover:hover){.custom-fields-component .fi-theme-switcher-btn:not(.fi-active):where(.dark,.dark *):hover{color:var(--gray-400)}}.custom-fields-component .fi-theme-switcher-btn:not(.fi-active):where(.dark,.dark *):focus-visible{color:var(--gray-400)}.custom-fields-component .fi-topbar-ctn{overflow-x:clip;position:sticky;top:calc(var(--spacing)*0);z-index:30}.custom-fields-component .fi-topbar{background-color:var(--color-white);min-height:calc(var(--spacing)*16);padding-inline:calc(var(--spacing)*4);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent);align-items:center;display:flex}.custom-fields-component .fi-topbar:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-topbar:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-topbar .fi-tenant-menu{display:none}@media (min-width:64rem){.custom-fields-component .fi-topbar .fi-tenant-menu{display:block}}.custom-fields-component .fi-topbar-close-sidebar-btn,.custom-fields-component .fi-topbar-open-sidebar-btn{margin-inline:calc(var(--spacing)*0)!important}@media (min-width:64rem){.custom-fields-component .fi-topbar-close-sidebar-btn{display:none}}.custom-fields-component .fi-topbar-open-collapse-sidebar-btn{margin-inline:calc(var(--spacing)*0)!important}.custom-fields-component .fi-topbar-close-collapse-sidebar-btn{display:none;margin-inline:calc(var(--spacing)*0)!important}@media (min-width:64rem){.custom-fields-component .fi-topbar-close-collapse-sidebar-btn{display:flex}}.custom-fields-component .fi-topbar-start{align-items:center;display:none;margin-inline-end:calc(var(--spacing)*6)}@media (min-width:64rem){.custom-fields-component .fi-topbar-start{display:flex}}.custom-fields-component .fi-topbar-start .fi-logo{margin-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-topbar-collapse-sidebar-btn-ctn{flex-shrink:0;width:calc(var(--spacing)*9)}@media (min-width:64rem){.custom-fields-component :is(.fi-body.fi-body-has-sidebar-collapsible-on-desktop,.fi-body:not(.fi-body-has-sidebar-fully-collapsible-on-desktop)) .fi-topbar-open-sidebar-btn{display:none}}.custom-fields-component .fi-topbar-nav-groups{align-items:center;column-gap:calc(var(--spacing)*4);display:none;margin-inline-end:calc(var(--spacing)*4);margin-inline-start:calc(var(--spacing)*4)}@media (min-width:64rem){.custom-fields-component .fi-topbar-nav-groups{display:flex;flex-wrap:wrap;margin-block:calc(var(--spacing)*2);row-gap:calc(var(--spacing)*1)}}.custom-fields-component .fi-topbar-end{align-items:center;column-gap:calc(var(--spacing)*4);display:flex;margin-inline-start:auto}.custom-fields-component .fi-topbar-item-btn{align-items:center;border-radius:var(--radius-lg);column-gap:calc(var(--spacing)*2);justify-content:center;padding-block:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*3);--tw-outline-style:none;display:flex;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-topbar-item-btn{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-topbar-item-btn{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-topbar-item-btn:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-topbar-item-btn:focus-visible{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-topbar-item-btn:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-topbar-item-btn:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}}.custom-fields-component .fi-topbar-item-btn:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-topbar-item-btn:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-topbar-item-btn>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-topbar-item-btn>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-topbar-item-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);color:var(--gray-700);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-topbar-item-label:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-btn{background-color:var(--gray-50)}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-btn:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-btn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-btn>.fi-icon{color:var(--primary-600)}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-btn>.fi-icon:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-label{color:var(--primary-600)}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-label:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-simple-user-menu-ctn{align-items:center;column-gap:calc(var(--spacing)*4);display:flex}.custom-fields-component .fi-topbar .fi-user-menu-trigger{flex-shrink:0}.custom-fields-component .fi-sidebar .fi-user-menu-trigger{align-items:center;border-radius:var(--radius-lg);column-gap:calc(var(--spacing)*3);font-size:var(--text-sm);justify-content:center;line-height:var(--tw-leading,var(--text-sm--line-height));padding:calc(var(--spacing)*2);width:100%;--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-outline-style:none;display:flex;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-sidebar .fi-user-menu-trigger{outline:2px solid #0000;outline-offset:2px}}.custom-fields-component .fi-sidebar .fi-user-menu-trigger{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-user-avatar{flex-shrink:0}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon{color:var(--gray-400);height:calc(var(--spacing)*5);transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));width:calc(var(--spacing)*5);--tw-duration:75ms;flex-shrink:0;margin-inline-start:auto;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon:is(:where(.group):hover *){color:var(--gray-500)}}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon:is(:where(.group):focus-visible *),.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon:where(.dark,.dark *){color:var(--gray-500)}@media (hover:hover){.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon:where(.dark,.dark *):is(:where(.group):hover *){color:var(--gray-400)}}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon:where(.dark,.dark *):is(:where(.group):focus-visible *){color:var(--gray-400)}.custom-fields-component .fi-sidebar .fi-user-menu-trigger:hover .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-sidebar .fi-user-menu-trigger:hover .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sidebar .fi-user-menu-trigger:focus-visible .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-sidebar .fi-user-menu-trigger:focus-visible .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-tenant-menu-trigger-text{color:var(--gray-950);display:grid;justify-items:start;text-align:start}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-tenant-menu-trigger-text:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sidebar .fi-user-menu .fi-dropdown-panel{max-width:max(14rem,100% - 1.5rem)!important}.custom-fields-component .fi-account-widget .fi-section-content{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-account-widget-logout-form{margin-block:auto}.custom-fields-component .fi-account-widget-main{flex:1}.custom-fields-component .fi-account-widget-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);color:var(--gray-950);display:grid;flex:1;font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-account-widget-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-account-widget-user-name{color:var(--gray-500);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-account-widget-user-name:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-filament-info-widget .fi-section-content{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-filament-info-widget-main{flex:1}.custom-fields-component .fi-filament-info-widget-logo{color:var(--gray-950);height:calc(var(--spacing)*5)}.custom-fields-component .fi-filament-info-widget-logo:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-filament-info-widget-version{color:var(--gray-500);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));margin-top:calc(var(--spacing)*2)}.custom-fields-component .fi-filament-info-widget-version:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-filament-info-widget-links{align-items:flex-end;display:flex;flex-direction:column;row-gap:calc(var(--spacing)*1)}}@layer utilities{.custom-fields-component .pointer-events-none{pointer-events:none}.custom-fields-component .visible{visibility:visible}.custom-fields-component .sr-only{border-width:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.custom-fields-component .absolute{position:absolute}.custom-fields-component .relative{position:relative}.custom-fields-component .static{position:static}.custom-fields-component .top-1\/2{top:50%}.custom-fields-component .right-0{right:calc(var(--spacing)*0)}.custom-fields-component .left-3{left:calc(var(--spacing)*3)}.custom-fields-component .z-50{z-index:50}.custom-fields-component .col-span-3{grid-column:span 3/span 3}.custom-fields-component .col-span-4{grid-column:span 4/span 4}.custom-fields-component .col-span-6{grid-column:span 6/span 6}.custom-fields-component .col-span-8{grid-column:span 8/span 8}.custom-fields-component .col-span-9{grid-column:span 9/span 9}.custom-fields-component .col-span-12{grid-column:span 12/span 12}.custom-fields-component .container{width:100%}@media (min-width:40rem){.custom-fields-component .container{max-width:40rem}}@media (min-width:48rem){.custom-fields-component .container{max-width:48rem}}@media (min-width:64rem){.custom-fields-component .container{max-width:64rem}}@media (min-width:80rem){.custom-fields-component .container{max-width:80rem}}@media (min-width:96rem){.custom-fields-component .container{max-width:96rem}}.custom-fields-component .-mx-1{margin-inline:calc(var(--spacing)*-1)}.custom-fields-component .mx-auto{margin-inline:auto}.custom-fields-component .mt-1{margin-top:calc(var(--spacing)*1)}.custom-fields-component .mt-6{margin-top:calc(var(--spacing)*6)}.custom-fields-component .mb-1{margin-bottom:calc(var(--spacing)*1)}.custom-fields-component .mb-2{margin-bottom:calc(var(--spacing)*2)}.custom-fields-component .mb-4{margin-bottom:calc(var(--spacing)*4)}.custom-fields-component .mb-6{margin-bottom:calc(var(--spacing)*6)}.custom-fields-component .ml-0\.5{margin-left:calc(var(--spacing)*.5)}.custom-fields-component .ml-2{margin-left:calc(var(--spacing)*2)}.custom-fields-component .ml-auto{margin-left:auto}.custom-fields-component .block{display:block}.custom-fields-component .flex{display:flex}.custom-fields-component .grid{display:grid}.custom-fields-component .hidden{display:none}.custom-fields-component .inline{display:inline}.custom-fields-component .inline-flex{display:inline-flex}.custom-fields-component .table{display:table}.custom-fields-component .size-3{height:calc(var(--spacing)*3);width:calc(var(--spacing)*3)}.custom-fields-component .size-3\.5{height:calc(var(--spacing)*3.5);width:calc(var(--spacing)*3.5)}.custom-fields-component .size-4{height:calc(var(--spacing)*4);width:calc(var(--spacing)*4)}.custom-fields-component .h-2\.5{height:calc(var(--spacing)*2.5)}.custom-fields-component .h-3{height:calc(var(--spacing)*3)}.custom-fields-component .h-3\.5{height:calc(var(--spacing)*3.5)}.custom-fields-component .h-4{height:calc(var(--spacing)*4)}.custom-fields-component .h-4\.5{height:calc(var(--spacing)*4.5)}.custom-fields-component .h-5{height:calc(var(--spacing)*5)}.custom-fields-component .h-6{height:calc(var(--spacing)*6)}.custom-fields-component .h-8{height:calc(var(--spacing)*8)}.custom-fields-component .h-full{height:100%}.custom-fields-component .max-h-48{max-height:calc(var(--spacing)*48)}.custom-fields-component .max-h-60{max-height:calc(var(--spacing)*60)}.custom-fields-component .max-h-\[280px\]{max-height:280px}.custom-fields-component .min-h-\[2\.25rem\]{min-height:2.25rem}.custom-fields-component .min-h-\[28px\]{min-height:28px}.custom-fields-component .min-h-\[50px\]{min-height:50px}.custom-fields-component .w-2\.5{width:calc(var(--spacing)*2.5)}.custom-fields-component .w-3{width:calc(var(--spacing)*3)}.custom-fields-component .w-3\.5{width:calc(var(--spacing)*3.5)}.custom-fields-component .w-4{width:calc(var(--spacing)*4)}.custom-fields-component .w-4\.5{width:calc(var(--spacing)*4.5)}.custom-fields-component .w-5{width:calc(var(--spacing)*5)}.custom-fields-component .w-6{width:calc(var(--spacing)*6)}.custom-fields-component .w-8{width:calc(var(--spacing)*8)}.custom-fields-component .w-20{width:calc(var(--spacing)*20)}.custom-fields-component .w-23{width:calc(var(--spacing)*23)}.custom-fields-component .w-64{width:calc(var(--spacing)*64)}.custom-fields-component .w-\[180px\]{width:180px}.custom-fields-component .w-\[220px\]{width:220px}.custom-fields-component .w-full{width:100%}.custom-fields-component .w-px{width:1px}.custom-fields-component .max-w-\[100px\]{max-width:100px}.custom-fields-component .max-w-\[120px\]{max-width:120px}.custom-fields-component .max-w-\[150px\]{max-width:150px}.custom-fields-component .max-w-\[250px\]{max-width:250px}.custom-fields-component .max-w-full{max-width:100%}.custom-fields-component .max-w-md{max-width:var(--container-md)}.custom-fields-component .max-w-sm{max-width:var(--container-sm)}.custom-fields-component .max-w-xs{max-width:var(--container-xs)}.custom-fields-component .min-w-0{min-width:calc(var(--spacing)*0)}.custom-fields-component .min-w-48{min-width:calc(var(--spacing)*48)}.custom-fields-component .min-w-\[600px\]{min-width:600px}.custom-fields-component .flex-1{flex:1}.custom-fields-component .shrink-0{flex-shrink:0}.custom-fields-component .-translate-y-1\/2{--tw-translate-y:-50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.custom-fields-component .rotate-180{rotate:180deg}.custom-fields-component .transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.custom-fields-component .cursor-grab{cursor:grab}.custom-fields-component .cursor-pointer{cursor:pointer}.custom-fields-component .grid-cols-\[40px_1fr\]{grid-template-columns:40px 1fr}.custom-fields-component .grid-cols-\[40px_1fr_minmax\(120px\,160px\)_minmax\(100px\,140px\)_minmax\(80px\,120px\)_50px\]{grid-template-columns:40px 1fr minmax(120px,160px) minmax(100px,140px) minmax(80px,120px) 50px}.custom-fields-component .flex-col{flex-direction:column}.custom-fields-component .flex-wrap{flex-wrap:wrap}.custom-fields-component .items-center{align-items:center}.custom-fields-component .justify-between{justify-content:space-between}.custom-fields-component .justify-center{justify-content:center}.custom-fields-component .justify-items-center{justify-items:center}.custom-fields-component .gap-1{gap:calc(var(--spacing)*1)}.custom-fields-component .gap-1\.5{gap:calc(var(--spacing)*1.5)}.custom-fields-component .gap-2{gap:calc(var(--spacing)*2)}.custom-fields-component .gap-4{gap:calc(var(--spacing)*4)}.custom-fields-component .gap-6{gap:calc(var(--spacing)*6)}.custom-fields-component :where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-end:calc(var(--spacing)*4*(1 - var(--tw-space-y-reverse)));margin-block-start:calc(var(--spacing)*4*var(--tw-space-y-reverse))}.custom-fields-component .gap-x-1{column-gap:calc(var(--spacing)*1)}.custom-fields-component .gap-x-2{column-gap:calc(var(--spacing)*2)}.custom-fields-component .gap-x-3{column-gap:calc(var(--spacing)*3)}.custom-fields-component .gap-x-4{column-gap:calc(var(--spacing)*4)}.custom-fields-component .gap-y-1{row-gap:calc(var(--spacing)*1)}.custom-fields-component .gap-y-2{row-gap:calc(var(--spacing)*2)}.custom-fields-component .gap-y-6{row-gap:calc(var(--spacing)*6)}.custom-fields-component :where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse))}.custom-fields-component :where(.divide-gray-200>:not(:last-child)){border-color:var(--gray-200)}.custom-fields-component .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.custom-fields-component .overflow-hidden{overflow:hidden}.custom-fields-component .overflow-x-auto{overflow-x:auto}.custom-fields-component .overflow-y-auto{overflow-y:auto}.custom-fields-component .rounded{border-radius:.25rem}.custom-fields-component .rounded-full{border-radius:3.40282e+38px}.custom-fields-component .rounded-lg{border-radius:var(--radius-lg)}.custom-fields-component .rounded-md{border-radius:var(--radius-md)}.custom-fields-component .rounded-xl{border-radius:var(--radius-xl)}.custom-fields-component .rounded-s-md{border-end-start-radius:var(--radius-md);border-start-start-radius:var(--radius-md)}.custom-fields-component .rounded-e-md{border-end-end-radius:var(--radius-md);border-start-end-radius:var(--radius-md)}.custom-fields-component .rounded-t-lg{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.custom-fields-component .rounded-l-lg{border-bottom-left-radius:var(--radius-lg);border-top-left-radius:var(--radius-lg)}.custom-fields-component .rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.custom-fields-component .border{border-style:var(--tw-border-style);border-width:1px}.custom-fields-component .border-0{border-style:var(--tw-border-style);border-width:0}.custom-fields-component .border-s{border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.custom-fields-component .border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.custom-fields-component .border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.custom-fields-component .border-dashed{--tw-border-style:dashed;border-style:dashed}.custom-fields-component .border-none{--tw-border-style:none;border-style:none}.custom-fields-component .border-gray-100{border-color:var(--gray-100)}.custom-fields-component .border-gray-200{border-color:var(--gray-200)}.custom-fields-component .border-gray-300{border-color:var(--gray-300)}.custom-fields-component .border-primary-600{border-color:var(--primary-600)}.custom-fields-component .bg-gray-50{background-color:var(--gray-50)}.custom-fields-component .bg-gray-100{background-color:var(--gray-100)}.custom-fields-component .bg-gray-200{background-color:var(--gray-200)}.custom-fields-component .bg-primary-50{background-color:var(--primary-50)}.custom-fields-component .bg-primary-600{background-color:var(--primary-600)}.custom-fields-component .bg-transparent{background-color:#0000}.custom-fields-component .bg-white{background-color:var(--color-white)}.custom-fields-component .bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.custom-fields-component .from-gray-100\/90{--tw-gradient-from:var(--gray-100)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .from-gray-100\/90{--tw-gradient-from:color-mix(in oklab,var(--gray-100)90%,transparent)}}.custom-fields-component .from-gray-100\/90{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.custom-fields-component .via-gray-100\/100{--tw-gradient-via:var(--gray-100);--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.custom-fields-component .to-gray-100{--tw-gradient-to:var(--gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.custom-fields-component .object-cover{object-fit:cover}.custom-fields-component .p-0{padding:calc(var(--spacing)*0)}.custom-fields-component .p-0\.5{padding:calc(var(--spacing)*.5)}.custom-fields-component .p-1{padding:calc(var(--spacing)*1)}.custom-fields-component .p-3{padding:calc(var(--spacing)*3)}.custom-fields-component .p-4{padding:calc(var(--spacing)*4)}.custom-fields-component .\!px-2{padding-inline:calc(var(--spacing)*2)!important}.custom-fields-component .px-1{padding-inline:calc(var(--spacing)*1)}.custom-fields-component .px-2{padding-inline:calc(var(--spacing)*2)}.custom-fields-component .px-3{padding-inline:calc(var(--spacing)*3)}.custom-fields-component .px-6{padding-inline:calc(var(--spacing)*6)}.custom-fields-component .\!py-2{padding-block:calc(var(--spacing)*2)!important}.custom-fields-component .py-0\.5{padding-block:calc(var(--spacing)*.5)}.custom-fields-component .py-1{padding-block:calc(var(--spacing)*1)}.custom-fields-component .py-1\.5{padding-block:calc(var(--spacing)*1.5)}.custom-fields-component .py-2{padding-block:calc(var(--spacing)*2)}.custom-fields-component .py-2\.5{padding-block:calc(var(--spacing)*2.5)}.custom-fields-component .py-3{padding-block:calc(var(--spacing)*3)}.custom-fields-component .py-6{padding-block:calc(var(--spacing)*6)}.custom-fields-component .py-12{padding-block:calc(var(--spacing)*12)}.custom-fields-component .py-16{padding-block:calc(var(--spacing)*16)}.custom-fields-component .pt-4{padding-top:calc(var(--spacing)*4)}.custom-fields-component .pr-1{padding-right:calc(var(--spacing)*1)}.custom-fields-component .pr-1\.5{padding-right:calc(var(--spacing)*1.5)}.custom-fields-component .pr-3{padding-right:calc(var(--spacing)*3)}.custom-fields-component .pl-2{padding-left:calc(var(--spacing)*2)}.custom-fields-component .pl-3{padding-left:calc(var(--spacing)*3)}.custom-fields-component .pl-9{padding-left:calc(var(--spacing)*9)}.custom-fields-component .text-center{text-align:center}.custom-fields-component .text-left{text-align:left}.custom-fields-component .text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .leading-5{--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5)}.custom-fields-component .leading-7{--tw-leading:calc(var(--spacing)*7);line-height:calc(var(--spacing)*7)}.custom-fields-component .leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.custom-fields-component .font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.custom-fields-component .whitespace-nowrap{white-space:nowrap}.custom-fields-component .text-gray-400{color:var(--gray-400)}.custom-fields-component .text-gray-500{color:var(--gray-500)}.custom-fields-component .text-gray-600{color:var(--gray-600)}.custom-fields-component .text-gray-700{color:var(--gray-700)}.custom-fields-component .text-gray-900{color:var(--gray-900)}.custom-fields-component .text-gray-950{color:var(--gray-950)}.custom-fields-component .text-green-500{color:var(--color-green-500)}.custom-fields-component .text-neutral-700{color:var(--color-neutral-700)}.custom-fields-component .text-primary-500{color:var(--primary-500)}.custom-fields-component .text-primary-600{color:var(--primary-600)}.custom-fields-component .text-primary-700{color:var(--primary-700)}.custom-fields-component .text-white{color:var(--color-white)}.custom-fields-component .uppercase{text-transform:uppercase}.custom-fields-component .underline{text-decoration-line:underline}.custom-fields-component .decoration-gray-300{-webkit-text-decoration-color:var(--gray-300);text-decoration-color:var(--gray-300)}.custom-fields-component .decoration-1{text-decoration-thickness:1px}.custom-fields-component .underline-offset-2{text-underline-offset:2px}.custom-fields-component .opacity-0{opacity:0}.custom-fields-component .opacity-60{opacity:.6}.custom-fields-component .opacity-70{opacity:.7}.custom-fields-component .shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a)}.custom-fields-component .shadow-lg,.custom-fields-component .shadow-none{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .shadow-none{--tw-shadow:0 0 #0000}.custom-fields-component .shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.custom-fields-component .ring-1,.custom-fields-component .shadow-sm{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor)}.custom-fields-component .ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .ring-gray-950\/5{--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .ring-gray-950\/5{--tw-ring-color:color-mix(in oklab,var(--gray-950)5%,transparent)}}.custom-fields-component .ring-primary-600{--tw-ring-color:var(--primary-600)}.custom-fields-component .filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.custom-fields-component .transition{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.custom-fields-component .transition-all{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.custom-fields-component .transition-colors{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.custom-fields-component .transition-opacity{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.custom-fields-component .transition-transform{transition-duration:var(--tw-duration,var(--default-transition-duration));transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function))}.custom-fields-component .duration-75{--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .duration-200{--tw-duration:.2s;transition-duration:.2s}.custom-fields-component .duration-300{--tw-duration:.3s;transition-duration:.3s}.custom-fields-component .outline-none{--tw-outline-style:none;outline-style:none}@media (hover:hover){.custom-fields-component .group-hover\/item\:opacity-100:is(:where(.group\/item):hover *),.custom-fields-component .group-hover\/value\:opacity-100:is(:where(.group\/value):hover *),.custom-fields-component .group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.custom-fields-component .placeholder\:text-gray-400::placeholder{color:var(--gray-400)}.custom-fields-component .first\:rounded-t-lg:first-child{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.custom-fields-component .last\:rounded-b-lg:last-child{border-bottom-left-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.custom-fields-component .last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.custom-fields-component .hover\:border-gray-400:hover{border-color:var(--gray-400)}.custom-fields-component .hover\:bg-danger-50:hover{background-color:var(--danger-50)}.custom-fields-component .hover\:bg-gray-50:hover{background-color:var(--gray-50)}.custom-fields-component .hover\:bg-gray-100:hover{background-color:var(--gray-100)}.custom-fields-component .hover\:bg-gray-300:hover{background-color:var(--gray-300)}.custom-fields-component .hover\:bg-primary-50:hover{background-color:var(--primary-50)}.custom-fields-component .hover\:bg-primary-600\/80:hover{background-color:var(--primary-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .hover\:bg-primary-600\/80:hover{background-color:color-mix(in oklab,var(--primary-600)80%,transparent)}}.custom-fields-component .hover\:text-danger-500:hover{color:var(--danger-500)}.custom-fields-component .hover\:text-gray-500:hover{color:var(--gray-500)}.custom-fields-component .hover\:text-gray-600:hover{color:var(--gray-600)}.custom-fields-component .hover\:text-primary-600:hover{color:var(--primary-600)}}.custom-fields-component .focus\:opacity-100:focus{opacity:1}.custom-fields-component .focus\:ring-0:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor)}.custom-fields-component .focus\:ring-0:focus,.custom-fields-component .focus\:ring-2:focus{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor)}.custom-fields-component .focus\:ring-primary-500:focus{--tw-ring-color:var(--primary-500)}.custom-fields-component .focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.custom-fields-component .focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.custom-fields-component .focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .focus-visible\:ring-primary-500:focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.custom-fields-component .active\:cursor-grabbing:active{cursor:grabbing}.custom-fields-component .disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.custom-fields-component .disabled\:text-gray-500:disabled{color:var(--gray-500)}.custom-fields-component .disabled\:opacity-50:disabled{opacity:.5}@media (hover:hover){.custom-fields-component .disabled\:hover\:bg-transparent:disabled:hover{background-color:#0000}}@media (min-width:40rem){.custom-fields-component .sm\:flex-row{flex-direction:row}.custom-fields-component .sm\:items-center{align-items:center}.custom-fields-component .sm\:justify-between{justify-content:space-between}}.custom-fields-component :where(.dark\:divide-white\/10:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.dark\:divide-white\/10:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .dark\:border-gray-600:where(.dark,.dark *){border-color:var(--gray-600)}.custom-fields-component .dark\:border-gray-800:where(.dark,.dark *){border-color:var(--gray-800)}.custom-fields-component .dark\:border-primary-500:where(.dark,.dark *){border-color:var(--primary-500)}.custom-fields-component .dark\:border-white\/10:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:border-white\/10:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .dark\:bg-gray-700:where(.dark,.dark *){background-color:var(--gray-700)}.custom-fields-component .dark\:bg-gray-800:where(.dark,.dark *),.custom-fields-component .dark\:bg-gray-800\/50:where(.dark,.dark *){background-color:var(--gray-800)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-gray-800\/50:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-800)50%,transparent)}}.custom-fields-component .dark\:bg-gray-900:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .dark\:bg-primary-500:where(.dark,.dark *),.custom-fields-component .dark\:bg-primary-500\/10:where(.dark,.dark *){background-color:var(--primary-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-primary-500\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--primary-500)10%,transparent)}}.custom-fields-component .dark\:bg-primary-950\/50:where(.dark,.dark *){background-color:var(--primary-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-primary-950\/50:where(.dark,.dark *){background-color:color-mix(in oklab,var(--primary-950)50%,transparent)}}.custom-fields-component .dark\:bg-white\/5:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-white\/5:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .dark\:from-gray-700\/0:where(.dark,.dark *){--tw-gradient-from:var(--gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:from-gray-700\/0:where(.dark,.dark *){--tw-gradient-from:color-mix(in oklab,var(--gray-700)0%,transparent)}}.custom-fields-component .dark\:from-gray-700\/0:where(.dark,.dark *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.custom-fields-component .dark\:from-gray-800\/0:where(.dark,.dark *){--tw-gradient-from:var(--gray-800)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:from-gray-800\/0:where(.dark,.dark *){--tw-gradient-from:color-mix(in oklab,var(--gray-800)0%,transparent)}}.custom-fields-component .dark\:from-gray-800\/0:where(.dark,.dark *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.custom-fields-component .dark\:via-gray-700\/70:where(.dark,.dark *){--tw-gradient-via:var(--gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:via-gray-700\/70:where(.dark,.dark *){--tw-gradient-via:color-mix(in oklab,var(--gray-700)70%,transparent)}}.custom-fields-component .dark\:via-gray-700\/70:where(.dark,.dark *){--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.custom-fields-component .dark\:via-gray-800\/70:where(.dark,.dark *){--tw-gradient-via:var(--gray-800)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:via-gray-800\/70:where(.dark,.dark *){--tw-gradient-via:color-mix(in oklab,var(--gray-800)70%,transparent)}}.custom-fields-component .dark\:via-gray-800\/70:where(.dark,.dark *){--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.custom-fields-component .dark\:to-gray-700:where(.dark,.dark *){--tw-gradient-to:var(--gray-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.custom-fields-component .dark\:to-gray-800:where(.dark,.dark *){--tw-gradient-to:var(--gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.custom-fields-component .dark\:text-gray-100:where(.dark,.dark *){color:var(--gray-100)}.custom-fields-component .dark\:text-gray-200:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .dark\:text-gray-300:where(.dark,.dark *){color:var(--gray-300)}.custom-fields-component .dark\:text-gray-400:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .dark\:text-gray-500:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .dark\:text-neutral-400:where(.dark,.dark *){color:var(--color-neutral-400)}.custom-fields-component .dark\:text-primary-400:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .dark\:text-white:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .dark\:decoration-gray-600:where(.dark,.dark *){-webkit-text-decoration-color:var(--gray-600);text-decoration-color:var(--gray-600)}.custom-fields-component .dark\:ring-primary-500:where(.dark,.dark *){--tw-ring-color:var(--primary-500)}.custom-fields-component .dark\:ring-white\/10:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:ring-white\/10:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .dark\:placeholder\:text-gray-500:where(.dark,.dark *)::placeholder{color:var(--gray-500)}@media (hover:hover){.custom-fields-component .dark\:hover\:bg-danger-500\/10:where(.dark,.dark *):hover{background-color:var(--danger-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-danger-500\/10:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--danger-500)10%,transparent)}}.custom-fields-component .dark\:hover\:bg-gray-600:where(.dark,.dark *):hover{background-color:var(--gray-600)}.custom-fields-component .dark\:hover\:bg-gray-700:where(.dark,.dark *):hover{background-color:var(--gray-700)}.custom-fields-component .dark\:hover\:bg-gray-800:where(.dark,.dark *):hover,.custom-fields-component .dark\:hover\:bg-gray-800\/50:where(.dark,.dark *):hover{background-color:var(--gray-800)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-gray-800\/50:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--gray-800)50%,transparent)}}.custom-fields-component .dark\:hover\:bg-primary-500\/10:where(.dark,.dark *):hover{background-color:var(--primary-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-primary-500\/10:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--primary-500)10%,transparent)}}.custom-fields-component .dark\:hover\:bg-white\/5:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-white\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white)5%,transparent)}}.custom-fields-component .dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--gray-300)}}.custom-fields-component .dark\:disabled\:text-gray-400:where(.dark,.dark *):disabled{color:var(--gray-400)}.custom-fields-component .\[\&_\.fi-badge\]\:ml-auto .fi-badge{margin-left:auto}.custom-fields-component .\[\&\:\:-webkit-scrollbar\]\:w-1\.5::-webkit-scrollbar{width:calc(var(--spacing)*1.5)}.custom-fields-component .\[\&\:\:-webkit-scrollbar-thumb\]\:rounded-full::-webkit-scrollbar-thumb{border-radius:3.40282e+38px}.custom-fields-component .\[\&\:\:-webkit-scrollbar-thumb\]\:bg-gray-300::-webkit-scrollbar-thumb{background-color:var(--gray-300)}.custom-fields-component .dark\:\[\&\:\:-webkit-scrollbar-thumb\]\:bg-gray-600:where(.dark,.dark *)::-webkit-scrollbar-thumb{background-color:var(--gray-600)}.custom-fields-component .fi-color-danger{--color-50:var(--danger-50);--color-100:var(--danger-100);--color-200:var(--danger-200);--color-300:var(--danger-300);--color-400:var(--danger-400);--color-500:var(--danger-500);--color-600:var(--danger-600);--color-700:var(--danger-700);--color-800:var(--danger-800);--color-900:var(--danger-900);--color-950:var(--danger-950)}.custom-fields-component .fi-color-gray{--color-50:var(--gray-50);--color-100:var(--gray-100);--color-200:var(--gray-200);--color-300:var(--gray-300);--color-400:var(--gray-400);--color-500:var(--gray-500);--color-600:var(--gray-600);--color-700:var(--gray-700);--color-800:var(--gray-800);--color-900:var(--gray-900);--color-950:var(--gray-950)}.custom-fields-component .fi-color-info{--color-50:var(--info-50);--color-100:var(--info-100);--color-200:var(--info-200);--color-300:var(--info-300);--color-400:var(--info-400);--color-500:var(--info-500);--color-600:var(--info-600);--color-700:var(--info-700);--color-800:var(--info-800);--color-900:var(--info-900);--color-950:var(--info-950)}.custom-fields-component .fi-color-primary{--color-50:var(--primary-50);--color-100:var(--primary-100);--color-200:var(--primary-200);--color-300:var(--primary-300);--color-400:var(--primary-400);--color-500:var(--primary-500);--color-600:var(--primary-600);--color-700:var(--primary-700);--color-800:var(--primary-800);--color-900:var(--primary-900);--color-950:var(--primary-950)}.custom-fields-component .fi-color-success{--color-50:var(--success-50);--color-100:var(--success-100);--color-200:var(--success-200);--color-300:var(--success-300);--color-400:var(--success-400);--color-500:var(--success-500);--color-600:var(--success-600);--color-700:var(--success-700);--color-800:var(--success-800);--color-900:var(--success-900);--color-950:var(--success-950)}.custom-fields-component .fi-color-warning{--color-50:var(--warning-50);--color-100:var(--warning-100);--color-200:var(--warning-200);--color-300:var(--warning-300);--color-400:var(--warning-400);--color-500:var(--warning-500);--color-600:var(--warning-600);--color-700:var(--warning-700);--color-800:var(--warning-800);--color-900:var(--warning-900);--color-950:var(--warning-950)}.custom-fields-component .fi-bg-color-50{--bg:var(--color-50)}.custom-fields-component .fi-bg-color-100{--bg:var(--color-100)}.custom-fields-component .fi-bg-color-200{--bg:var(--color-200)}.custom-fields-component .fi-bg-color-300{--bg:var(--color-300)}.custom-fields-component .fi-bg-color-400{--bg:var(--color-400)}.custom-fields-component .fi-bg-color-500{--bg:var(--color-500)}.custom-fields-component .fi-bg-color-600{--bg:var(--color-600)}.custom-fields-component .fi-bg-color-700{--bg:var(--color-700)}.custom-fields-component .fi-bg-color-800{--bg:var(--color-800)}.custom-fields-component .fi-bg-color-900{--bg:var(--color-900)}.custom-fields-component .fi-bg-color-950{--bg:var(--color-950)}.custom-fields-component .hover\:fi-bg-color-50{--hover-bg:var(--color-50)}.custom-fields-component .hover\:fi-bg-color-100{--hover-bg:var(--color-100)}.custom-fields-component .hover\:fi-bg-color-200{--hover-bg:var(--color-200)}.custom-fields-component .hover\:fi-bg-color-300{--hover-bg:var(--color-300)}.custom-fields-component .hover\:fi-bg-color-400{--hover-bg:var(--color-400)}.custom-fields-component .hover\:fi-bg-color-500{--hover-bg:var(--color-500)}.custom-fields-component .hover\:fi-bg-color-600{--hover-bg:var(--color-600)}.custom-fields-component .hover\:fi-bg-color-700{--hover-bg:var(--color-700)}.custom-fields-component .hover\:fi-bg-color-800{--hover-bg:var(--color-800)}.custom-fields-component .hover\:fi-bg-color-900{--hover-bg:var(--color-900)}.custom-fields-component .hover\:fi-bg-color-950{--hover-bg:var(--color-950)}.custom-fields-component .dark\:fi-bg-color-50{--dark-bg:var(--color-50)}.custom-fields-component .dark\:fi-bg-color-100{--dark-bg:var(--color-100)}.custom-fields-component .dark\:fi-bg-color-200{--dark-bg:var(--color-200)}.custom-fields-component .dark\:fi-bg-color-300{--dark-bg:var(--color-300)}.custom-fields-component .dark\:fi-bg-color-400{--dark-bg:var(--color-400)}.custom-fields-component .dark\:fi-bg-color-500{--dark-bg:var(--color-500)}.custom-fields-component .dark\:fi-bg-color-600{--dark-bg:var(--color-600)}.custom-fields-component .dark\:fi-bg-color-700{--dark-bg:var(--color-700)}.custom-fields-component .dark\:fi-bg-color-800{--dark-bg:var(--color-800)}.custom-fields-component .dark\:fi-bg-color-900{--dark-bg:var(--color-900)}.custom-fields-component .dark\:fi-bg-color-950{--dark-bg:var(--color-950)}.custom-fields-component .dark\:hover\:fi-bg-color-50{--dark-hover-bg:var(--color-50)}.custom-fields-component .dark\:hover\:fi-bg-color-100{--dark-hover-bg:var(--color-100)}.custom-fields-component .dark\:hover\:fi-bg-color-200{--dark-hover-bg:var(--color-200)}.custom-fields-component .dark\:hover\:fi-bg-color-300{--dark-hover-bg:var(--color-300)}.custom-fields-component .dark\:hover\:fi-bg-color-400{--dark-hover-bg:var(--color-400)}.custom-fields-component .dark\:hover\:fi-bg-color-500{--dark-hover-bg:var(--color-500)}.custom-fields-component .dark\:hover\:fi-bg-color-600{--dark-hover-bg:var(--color-600)}.custom-fields-component .dark\:hover\:fi-bg-color-700{--dark-hover-bg:var(--color-700)}.custom-fields-component .dark\:hover\:fi-bg-color-800{--dark-hover-bg:var(--color-800)}.custom-fields-component .dark\:hover\:fi-bg-color-900{--dark-hover-bg:var(--color-900)}.custom-fields-component .dark\:hover\:fi-bg-color-950{--dark-hover-bg:var(--color-950)}.custom-fields-component .fi-text-color-0{--text:oklch(100% 0 0)}.custom-fields-component .fi-text-color-50{--text:var(--color-50)}.custom-fields-component .fi-text-color-100{--text:var(--color-100)}.custom-fields-component .fi-text-color-200{--text:var(--color-200)}.custom-fields-component .fi-text-color-300{--text:var(--color-300)}.custom-fields-component .fi-text-color-400{--text:var(--color-400)}.custom-fields-component .fi-text-color-500{--text:var(--color-500)}.custom-fields-component .fi-text-color-600{--text:var(--color-600)}.custom-fields-component .fi-text-color-700{--text:var(--color-700)}.custom-fields-component .fi-text-color-800{--text:var(--color-800)}.custom-fields-component .fi-text-color-900{--text:var(--color-900)}.custom-fields-component .fi-text-color-950{--text:var(--color-950)}.custom-fields-component .hover\:fi-text-color-0{--hover-text:oklch(100% 0 0)}.custom-fields-component .hover\:fi-text-color-50{--hover-text:var(--color-50)}.custom-fields-component .hover\:fi-text-color-100{--hover-text:var(--color-100)}.custom-fields-component .hover\:fi-text-color-200{--hover-text:var(--color-200)}.custom-fields-component .hover\:fi-text-color-300{--hover-text:var(--color-300)}.custom-fields-component .hover\:fi-text-color-400{--hover-text:var(--color-400)}.custom-fields-component .hover\:fi-text-color-500{--hover-text:var(--color-500)}.custom-fields-component .hover\:fi-text-color-600{--hover-text:var(--color-600)}.custom-fields-component .hover\:fi-text-color-700{--hover-text:var(--color-700)}.custom-fields-component .hover\:fi-text-color-800{--hover-text:var(--color-800)}.custom-fields-component .hover\:fi-text-color-900{--hover-text:var(--color-900)}.custom-fields-component .hover\:fi-text-color-950{--hover-text:var(--color-950)}.custom-fields-component .dark\:fi-text-color-0{--dark-text:oklch(100% 0 0)}.custom-fields-component .dark\:fi-text-color-50{--dark-text:var(--color-50)}.custom-fields-component .dark\:fi-text-color-100{--dark-text:var(--color-100)}.custom-fields-component .dark\:fi-text-color-200{--dark-text:var(--color-200)}.custom-fields-component .dark\:fi-text-color-300{--dark-text:var(--color-300)}.custom-fields-component .dark\:fi-text-color-400{--dark-text:var(--color-400)}.custom-fields-component .dark\:fi-text-color-500{--dark-text:var(--color-500)}.custom-fields-component .dark\:fi-text-color-600{--dark-text:var(--color-600)}.custom-fields-component .dark\:fi-text-color-700{--dark-text:var(--color-700)}.custom-fields-component .dark\:fi-text-color-800{--dark-text:var(--color-800)}.custom-fields-component .dark\:fi-text-color-900{--dark-text:var(--color-900)}.custom-fields-component .dark\:fi-text-color-950{--dark-text:var(--color-950)}.custom-fields-component .dark\:hover\:fi-text-color-0{--dark-hover-text:oklch(100% 0 0)}.custom-fields-component .dark\:hover\:fi-text-color-50{--dark-hover-text:var(--color-50)}.custom-fields-component .dark\:hover\:fi-text-color-100{--dark-hover-text:var(--color-100)}.custom-fields-component .dark\:hover\:fi-text-color-200{--dark-hover-text:var(--color-200)}.custom-fields-component .dark\:hover\:fi-text-color-300{--dark-hover-text:var(--color-300)}.custom-fields-component .dark\:hover\:fi-text-color-400{--dark-hover-text:var(--color-400)}.custom-fields-component .dark\:hover\:fi-text-color-500{--dark-hover-text:var(--color-500)}.custom-fields-component .dark\:hover\:fi-text-color-600{--dark-hover-text:var(--color-600)}.custom-fields-component .dark\:hover\:fi-text-color-700{--dark-hover-text:var(--color-700)}.custom-fields-component .dark\:hover\:fi-text-color-800{--dark-hover-text:var(--color-800)}.custom-fields-component .dark\:hover\:fi-text-color-900{--dark-hover-text:var(--color-900)}.custom-fields-component .dark\:hover\:fi-text-color-950{--dark-hover-text:var(--color-950)}.custom-fields-component .fi-sr-only{border-width:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.custom-fields-component .fi-prose{--prose-color:var(--color-gray-700);--prose-heading-color:var(--color-gray-950);--prose-strong-color:var(--color-gray-950);--prose-link-color:var(--color-gray-950);--prose-code-color:var(--color-gray-950);--prose-marker-color:var(--color-gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose{--prose-marker-color:color-mix(in oklab,var(--color-gray-700)25%,transparent)}}.custom-fields-component .fi-prose{--prose-link-underline-color:var(--color-primary-400);--prose-th-borders:var(--color-gray-300);--prose-td-borders:var(--color-gray-200);--prose-hr-color:var(--color-gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose{--prose-hr-color:color-mix(in oklab,var(--color-gray-950)5%,transparent)}}.custom-fields-component .fi-prose{--prose-blockquote-border-color:var(--color-gray-300);--prose-pre-bg:var(--color-gray-100)}.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-color:var(--color-gray-300);--prose-heading-color:var(--color-white);--prose-strong-color:var(--color-white);--prose-link-color:var(--color-white);--prose-code-color:var(--color-white);--prose-marker-color:var(--color-gray-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-marker-color:color-mix(in oklab,var(--color-gray-300)35%,transparent)}}.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-link-underline-color:var(--color-sky-400);--prose-th-borders:var(--color-gray-600);--prose-td-borders:var(--color-gray-700);--prose-hr-color:oklab(100% 0 5.96046e-8/.1)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-hr-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-blockquote-border-color:var(--color-gray-600);--prose-pre-bg:var(--color-gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-pre-bg:color-mix(in oklab,var(--color-gray-900)40%,transparent)}}.custom-fields-component .fi-prose{color:var(--prose-color);font-size:var(--text-sm);line-height:1.5}.custom-fields-component .fi-prose img+img{margin-top:0}.custom-fields-component .fi-prose :where(:not(.fi-not-prose,.fi-not-prose *,br))+:where(:not(.fi-not-prose,.fi-not-prose *,br)){margin-top:calc(var(--spacing)*4)}.custom-fields-component .fi-prose p br{margin:0}.custom-fields-component .fi-prose h1:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-code-color);font-size:var(--text-xl);font-weight:var(--font-weight-bold);letter-spacing:-.025em;line-height:1.55556}.custom-fields-component .fi-prose h2:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-code-color);font-size:var(--text-lg);font-weight:var(--font-weight-semibold);letter-spacing:-.025em;line-height:1.55556}.custom-fields-component .fi-prose h3:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-heading-color);font-size:var(--text-base);font-weight:var(--font-weight-semibold);line-height:1.55556}.custom-fields-component .fi-prose h4:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose h5:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose h6:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-heading-color);font-size:var(--text-sm);font-weight:var(--font-weight-semibold);line-height:2}.custom-fields-component .fi-prose :is(h2,h3,h4,h5,h6):where(:not(.fi-not-prose,.fi-not-prose *)){scroll-margin-top:calc(var(--spacing)*32)}@media (min-width:64rem){.custom-fields-component .fi-prose :is(h2,h3,h4,h5,h6):where(:not(.fi-not-prose,.fi-not-prose *)){scroll-margin-top:calc(var(--spacing)*18)}}.custom-fields-component .fi-prose ol:where(:not(.fi-not-prose,.fi-not-prose *)){list-style-type:decimal;padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-prose ul:where(:not(.fi-not-prose,.fi-not-prose *)){list-style-type:disc;padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-prose ol li:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose ul li:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-prose ol li+li:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose ul li+li:where(:not(.fi-not-prose,.fi-not-prose *)){margin-top:calc(var(--spacing)*4)}.custom-fields-component .fi-prose ol li:where(:not(.fi-not-prose,.fi-not-prose *))::marker{color:var(--prose-marker-color)}.custom-fields-component .fi-prose ul li:where(:not(.fi-not-prose,.fi-not-prose *))::marker{color:var(--prose-marker-color)}.custom-fields-component .fi-prose a:not(:where(:is(h2,h3,h4,h5,h6) *)):where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-link-color);font-weight:var(--font-weight-semibold);text-decoration:underline;-webkit-text-decoration-color:var(--prose-link-underline-color);text-decoration-color:var(--prose-link-underline-color);text-decoration-thickness:1px;text-underline-offset:3px}.custom-fields-component .fi-prose a:not(:where(:is(h2,h3,h4,h5,h6) *)):where(:not(.fi-not-prose,.fi-not-prose *)) code{font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-prose a:hover:where(:not(.fi-not-prose,.fi-not-prose *)){text-decoration-thickness:2px}.custom-fields-component .fi-prose strong:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-strong-color);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-prose code:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-code-color);font-family:var(--font-mono);font-variant-ligatures:none;font-weight:var(--font-weight-medium)}.custom-fields-component .fi-prose :where(h2,h3,h4,h5,h6) code:where(:not(.fi-not-prose,.fi-not-prose *)){font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-prose code:where(:not(.fi-not-prose,.fi-not-prose *)):after,.custom-fields-component .fi-prose code:where(:not(.fi-not-prose,.fi-not-prose *)):before{content:"`";display:inline}.custom-fields-component .fi-prose pre:where(:not(.fi-not-prose,.fi-not-prose *)){background-color:var(--prose-pre-bg);border-radius:var(--radius-lg);margin-bottom:calc(var(--spacing)*10);margin-top:calc(var(--spacing)*4);padding-inline-end:calc(var(--spacing)*4);padding-bottom:calc(var(--spacing)*3);padding-top:calc(var(--spacing)*3);padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-prose pre code *+:where(:not(.fi-not-prose,.fi-not-prose *)){margin-top:0}.custom-fields-component .fi-prose pre code:where(:not(.fi-not-prose,.fi-not-prose *)):after,.custom-fields-component .fi-prose pre code:where(:not(.fi-not-prose,.fi-not-prose *)):before{content:none}.custom-fields-component .fi-prose pre code:where(:not(.fi-not-prose,.fi-not-prose *)){font-family:var(--font-mono);font-size:var(--text-sm);font-variant-ligatures:none;line-height:2}.custom-fields-component .fi-prose table:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:var(--text-sm);line-height:1.4;margin-bottom:2em;margin-top:2em;table-layout:auto;width:100%}.custom-fields-component .fi-prose thead:where(:not(.fi-not-prose,.fi-not-prose *)){border-bottom-color:var(--prose-th-borders);border-bottom-width:1px}.custom-fields-component .fi-prose thead th:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-heading-color);font-weight:600;padding-inline-end:.6em;padding-bottom:.8em;padding-inline-start:.6em;vertical-align:bottom}.custom-fields-component .fi-prose thead th:first-child:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-start:0}.custom-fields-component .fi-prose thead th:last-child:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-end:0}.custom-fields-component .fi-prose tbody tr:where(:not(.fi-not-prose,.fi-not-prose *)){border-bottom-color:var(--prose-td-borders);border-bottom-width:1px}.custom-fields-component .fi-prose tbody tr:last-child:where(:not(.fi-not-prose,.fi-not-prose *)){border-bottom-width:0}.custom-fields-component .fi-prose tbody td:where(:not(.fi-not-prose,.fi-not-prose *)){vertical-align:baseline}.custom-fields-component .fi-prose tfoot:where(:not(.fi-not-prose,.fi-not-prose *)){border-top-color:var(--prose-th-borders);border-top-width:1px}.custom-fields-component .fi-prose tfoot td:where(:not(.fi-not-prose,.fi-not-prose *)){vertical-align:top}.custom-fields-component .fi-prose tbody td:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose tfoot td:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-end:.6em;padding-bottom:.8em;padding-top:.8em;padding-inline-start:.6em}.custom-fields-component .fi-prose tbody td:first-child:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose tfoot td:first-child:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-start:0}.custom-fields-component .fi-prose tbody td:last-child:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose tfoot td:last-child:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-end:0}.custom-fields-component .fi-prose td:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose th:where(:not(.fi-not-prose,.fi-not-prose *)){text-align:start}.custom-fields-component .fi-prose td code:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:.8125rem}.custom-fields-component .fi-prose hr:where(:not(.fi-not-prose,.fi-not-prose *)){border-color:var(--prose-hr-color);margin-block:calc(var(--spacing)*8)}.custom-fields-component .fi-prose hr:where(:not(.fi-not-prose,.fi-not-prose *))+h2{margin-top:calc(var(--spacing)*8)}.custom-fields-component .fi-prose blockquote{border-inline-start-color:var(--prose-blockquote-border-color);border-inline-start-width:.25rem;font-style:italic;padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-prose blockquote p:first-of-type:before{content:open-quote}.custom-fields-component .fi-prose blockquote p:last-of-type:after{content:close-quote}.custom-fields-component .fi-prose figure:where(:not(.fi-not-prose,.fi-not-prose *)) figcaption:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-color);font-size:var(--text-sm);font-style:italic;line-height:var(--text-sm--line-height);margin-top:calc(var(--spacing)*3);text-align:center}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose figure:where(:not(.fi-not-prose,.fi-not-prose *)) figcaption:where(:not(.fi-not-prose,.fi-not-prose *)){color:color-mix(in oklab,var(--prose-color)75%,transparent)}}.custom-fields-component .fi-prose :first-child:where(:not(.fi-not-prose,.fi-not-prose *)){margin-top:0}.custom-fields-component .fi-prose :last-child:where(:not(.fi-not-prose,.fi-not-prose *)){margin-bottom:0}.custom-fields-component .fi-prose .color:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--color)}.custom-fields-component .fi-prose .color:where(:not(.fi-not-prose,.fi-not-prose *)):where(.dark,.dark *){color:var(--dark-color)}.custom-fields-component .fi-prose .lead:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:var(--text-base)}.custom-fields-component .fi-prose a[data-type=mention]:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose span[data-type=mention]:where(:not(.fi-not-prose,.fi-not-prose *)){display:inline-block;font-weight:var(--font-weight-semibold);margin-block:0;white-space:nowrap}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *)){display:grid;gap:calc(var(--spacing)*4);grid-template-columns:repeat(1,minmax(0,1fr))}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=default]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=default]>.grid-layout-col{grid-column:var(--col-span)}@media (min-width:40rem){.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=sm]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=sm]>.grid-layout-col{grid-column:var(--col-span)}}@media (min-width:48rem){.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=md]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=md]>.grid-layout-col{grid-column:var(--col-span)}}@media (min-width:64rem){.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=lg]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=lg]>.grid-layout-col{grid-column:var(--col-span)}}@media (min-width:80rem){.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=xl]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=xl]>.grid-layout-col{grid-column:var(--col-span)}}@media (min-width:96rem){.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint="2xl"]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint="2xl"]>.grid-layout-col{grid-column:var(--col-span)}}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))>.grid-layout-col{margin-top:0;min-width:0}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-ease{syntax:"*";inherits:false}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@keyframes spin{to{transform:rotate(1turn)}}@keyframes pulse{50%{opacity:.5}} \ No newline at end of file +/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){.custom-fields-component *,.custom-fields-component ::backdrop,.custom-fields-component :after,.custom-fields-component :before{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-outline-style:solid;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-ease:initial;--tw-divide-x-reverse:0;--tw-content:"";--tw-space-x-reverse:0;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial}}}@layer theme{.custom-fields-component,.custom-fields-component :host{--font-mono:var(--mono-font-family), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-green-500:oklch(72.3% .219 149.579);--color-sky-400:oklch(74.6% .16 232.661);--color-gray-100:var(--gray-100);--color-gray-200:var(--gray-200);--color-gray-300:var(--gray-300);--color-gray-400:var(--gray-400);--color-gray-500:var(--gray-500);--color-gray-600:var(--gray-600);--color-gray-700:var(--gray-700);--color-gray-900:var(--gray-900);--color-gray-950:var(--gray-950);--color-neutral-400:oklch(70.8% 0 none);--color-neutral-700:oklch(37.1% 0 none);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-sm:40rem;--breakpoint-md:48rem;--breakpoint-lg:64rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-3xs:16rem;--container-2xs:18rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:1.33333;--text-sm:.875rem;--text-sm--line-height:1.42857;--text-base:1rem;--text-base--line-height:1.5;--text-lg:1.125rem;--text-lg--line-height:1.55556;--text-xl:1.25rem;--text-xl--line-height:1.4;--text-2xl:1.5rem;--text-2xl--line-height:1.33333;--text-3xl:1.875rem;--text-3xl--line-height:1.2;--font-weight-thin:100;--font-weight-extralight:200;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-wide:.025em;--leading-relaxed:1.625;--leading-loose:2;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-family), ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--default-mono-font-family:var(--mono-font-family), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-primary-400:var(--primary-400)}}@layer base{.custom-fields-component *,.custom-fields-component ::backdrop,.custom-fields-component :after,.custom-fields-component :before{box-sizing:border-box;border:0 solid;margin:0;padding:0}.custom-fields-component ::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}.custom-fields-component,.custom-fields-component :host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}.custom-fields-component hr{height:0;color:inherit;border-top-width:1px}.custom-fields-component abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.custom-fields-component h1,.custom-fields-component h2,.custom-fields-component h3,.custom-fields-component h4,.custom-fields-component h5,.custom-fields-component h6{font-size:inherit;font-weight:inherit}.custom-fields-component a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}.custom-fields-component b,.custom-fields-component strong{font-weight:bolder}.custom-fields-component code,.custom-fields-component kbd,.custom-fields-component pre,.custom-fields-component samp{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}.custom-fields-component small{font-size:80%}.custom-fields-component sub,.custom-fields-component sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}.custom-fields-component sub{bottom:-.25em}.custom-fields-component sup{top:-.5em}.custom-fields-component table{text-indent:0;border-color:inherit;border-collapse:collapse}.custom-fields-component :-moz-focusring:where(:not(iframe)){outline:auto}.custom-fields-component progress{vertical-align:baseline}.custom-fields-component summary{display:list-item}.custom-fields-component menu,.custom-fields-component ol,.custom-fields-component ul{list-style:none}.custom-fields-component audio,.custom-fields-component canvas,.custom-fields-component embed,.custom-fields-component iframe,.custom-fields-component img,.custom-fields-component object,.custom-fields-component svg,.custom-fields-component video{vertical-align:middle;display:block}.custom-fields-component img,.custom-fields-component video{max-width:100%;height:auto}.custom-fields-component button,.custom-fields-component input,.custom-fields-component optgroup,.custom-fields-component select,.custom-fields-component textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}.custom-fields-component ::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}.custom-fields-component :where(select:is([multiple],[size])) optgroup{font-weight:bolder}.custom-fields-component :where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}.custom-fields-component ::file-selector-button{margin-inline-end:4px}.custom-fields-component ::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){.custom-fields-component ::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){.custom-fields-component ::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}.custom-fields-component textarea{resize:vertical}.custom-fields-component ::-webkit-search-decoration{-webkit-appearance:none}.custom-fields-component ::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}.custom-fields-component ::-webkit-datetime-edit{display:inline-flex}.custom-fields-component ::-webkit-datetime-edit-fields-wrapper{padding:0}.custom-fields-component ::-webkit-datetime-edit,.custom-fields-component ::-webkit-datetime-edit-year-field{padding-block:0}.custom-fields-component ::-webkit-datetime-edit-day-field,.custom-fields-component ::-webkit-datetime-edit-month-field{padding-block:0}.custom-fields-component ::-webkit-datetime-edit-hour-field,.custom-fields-component ::-webkit-datetime-edit-minute-field{padding-block:0}.custom-fields-component ::-webkit-datetime-edit-millisecond-field,.custom-fields-component ::-webkit-datetime-edit-second-field{padding-block:0}.custom-fields-component ::-webkit-datetime-edit-meridiem-field{padding-block:0}.custom-fields-component ::-webkit-calendar-picker-indicator{line-height:1}.custom-fields-component :-moz-ui-invalid{box-shadow:none}.custom-fields-component button,.custom-fields-component input:where([type=button],[type=reset],[type=submit]){appearance:button}.custom-fields-component ::file-selector-button{appearance:button}.custom-fields-component ::-webkit-inner-spin-button,.custom-fields-component ::-webkit-outer-spin-button{height:auto}.custom-fields-component [hidden]:where(:not([hidden=until-found])){display:none!important}.custom-fields-component [role=button]:not(:disabled),.custom-fields-component button:not(:disabled){cursor:pointer}@media (prefers-reduced-motion:reduce){.custom-fields-component *,.custom-fields-component :after,.custom-fields-component :before{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.custom-fields-component.dark{color-scheme:dark}.custom-fields-component [data-field-wrapper],.custom-fields-component [data-field-wrapper] *{scroll-margin-top:calc(var(--topbar-height) + 4rem)}}@layer components{.custom-fields-component .tippy-box[data-animation=fade][data-state=hidden]{opacity:0}.custom-fields-component [data-tippy-root]{max-width:calc(100vw - 10px)}.custom-fields-component .tippy-box{color:#fff;white-space:normal;background-color:#333;border-radius:4px;outline:0;font-size:14px;line-height:1.4;transition-property:transform,visibility,opacity;position:relative}.custom-fields-component .tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.custom-fields-component .tippy-box[data-placement^=top]>.tippy-arrow:before{border-width:8px 8px 0;border-top-color:initial;transform-origin:top;bottom:-7px;left:0}.custom-fields-component .tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.custom-fields-component .tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-width:0 8px 8px;border-bottom-color:initial;transform-origin:bottom;top:-7px;left:0}.custom-fields-component .tippy-box[data-placement^=left]>.tippy-arrow{right:0}.custom-fields-component .tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;transform-origin:0;right:-7px}.custom-fields-component .tippy-box[data-placement^=right]>.tippy-arrow{left:0}.custom-fields-component .tippy-box[data-placement^=right]>.tippy-arrow:before{border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:100%;left:-7px}.custom-fields-component .tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.custom-fields-component .tippy-arrow{color:#333;width:16px;height:16px}.custom-fields-component .tippy-arrow:before{content:"";border-style:solid;border-color:#0000;position:absolute}.custom-fields-component .tippy-content{z-index:1;padding:5px 9px;position:relative}.custom-fields-component .tippy-box[data-theme~=light]{color:#26323d;background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926}.custom-fields-component .tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.custom-fields-component .tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.custom-fields-component .fi-avatar{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8);border-radius:var(--radius-md);object-fit:cover;object-position:center}.custom-fields-component .fi-avatar.fi-circular{border-radius:3.40282e+38px}.custom-fields-component .fi-avatar.fi-size-sm{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.custom-fields-component .fi-avatar.fi-size-lg{width:calc(var(--spacing)*10);height:calc(var(--spacing)*10)}.custom-fields-component .fi-badge{justify-content:center;align-items:center;column-gap:var(--spacing);border-radius:var(--radius-md);background-color:var(--gray-50);min-width:1.5rem;padding-inline:calc(var(--spacing)*2);padding-block:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-600);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-600);display:inline-flex}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge{--tw-ring-color:color-mix(in oklab, var(--gray-600) 10%, transparent)}}.custom-fields-component .fi-badge{--tw-ring-inset:inset}.custom-fields-component .fi-badge:where(.dark,.dark *){background-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-400) 10%,transparent)}}.custom-fields-component .fi-badge:where(.dark,.dark *){color:var(--gray-200);--tw-ring-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--gray-400) 20%, transparent)}}.custom-fields-component .fi-badge:not(.fi-wrapped){text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.custom-fields-component .fi-badge.fi-disabled:not(.fi-force-enabled),.custom-fields-component .fi-badge[disabled]:not(.fi-force-enabled){cursor:default;opacity:.7}.custom-fields-component :is(.fi-badge.fi-disabled:not(.fi-force-enabled),.fi-badge[disabled]:not(.fi-force-enabled)):not([x-tooltip]){pointer-events:none}.custom-fields-component .fi-badge .fi-badge-label-ctn{align-self:baseline;display:grid}.custom-fields-component .fi-badge .fi-badge-label.fi-wrapped{text-wrap:wrap;word-break:break-word}.custom-fields-component .fi-badge .fi-badge-label:not(.fi-wrapped){text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.custom-fields-component .fi-badge .fi-icon{flex-shrink:0}.custom-fields-component .fi-badge.fi-size-xs{min-width:1rem;padding-inline:calc(var(--spacing)*.5);--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter);padding-block:0}.custom-fields-component .fi-badge.fi-size-sm{min-width:1.25rem;padding-inline:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*.5);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.custom-fields-component .fi-badge.fi-color{background-color:var(--color-50);color:var(--text);--tw-ring-color:var(--color-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge.fi-color{--tw-ring-color:color-mix(in oklab, var(--color-600) 10%, transparent)}}.custom-fields-component .fi-badge.fi-color:where(.dark,.dark *){background-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge.fi-color:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-400) 10%,transparent)}}.custom-fields-component .fi-badge.fi-color:where(.dark,.dark *){color:var(--dark-text);--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge.fi-color:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-400) 30%, transparent)}}.custom-fields-component .fi-badge.fi-color .fi-badge-delete-btn-icon{color:var(--color-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge.fi-color .fi-badge-delete-btn-icon{color:color-mix(in oklab,var(--color-700) 50%,transparent)}}.custom-fields-component .fi-badge.fi-color .fi-badge-delete-btn-icon:where(.dark,.dark *){color:var(--color-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge.fi-color .fi-badge-delete-btn-icon:where(.dark,.dark *){color:color-mix(in oklab,var(--color-300) 50%,transparent)}}.custom-fields-component .fi-badge:not(.fi-color) .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-badge:not(.fi-color) .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-badge .fi-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-badge .fi-badge-delete-btn{margin-block:calc(var(--spacing)*-1);padding:var(--spacing);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;--tw-outline-style:none;outline-style:none;justify-content:center;align-items:center;margin-inline-start:calc(var(--spacing)*-1);margin-inline-end:calc(var(--spacing)*-2);transition-duration:75ms;display:flex}.custom-fields-component .fi-badge .fi-badge-delete-btn-icon{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3);color:var(--gray-700);flex-shrink:0}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge .fi-badge-delete-btn-icon{color:color-mix(in oklab,var(--gray-700) 50%,transparent)}}.custom-fields-component .fi-badge .fi-badge-delete-btn-icon:where(.dark,.dark *){color:var(--gray-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-badge .fi-badge-delete-btn-icon:where(.dark,.dark *){color:color-mix(in oklab,var(--gray-300) 50%,transparent)}}.custom-fields-component .fi-badge .fi-badge-delete-btn-icon{background-color:currentColor;-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M5.28%204.22a.75.75%200%200%200-1.06%201.06L6.94%208l-2.72%202.72a.75.75%200%201%200%201.06%201.06L8%209.06l2.72%202.72a.75.75%200%201%200%201.06-1.06L9.06%208l2.72-2.72a.75.75%200%200%200-1.06-1.06L8%206.94z%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M5.28%204.22a.75.75%200%200%200-1.06%201.06L6.94%208l-2.72%202.72a.75.75%200%201%200%201.06%201.06L8%209.06l2.72%202.72a.75.75%200%201%200%201.06-1.06L9.06%208l2.72-2.72a.75.75%200%200%200-1.06-1.06L8%206.94z%22%2F%3E%3C%2Fsvg%3E");-webkit-mask-position:50%;mask-position:50%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn-icon:focus-visible{color:var(--gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn-icon:focus-visible{color:color-mix(in oklab,var(--gray-700) 75%,transparent)}}@media (hover:hover){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn-icon:where(.dark,.dark *):hover{color:var(--gray-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn-icon:where(.dark,.dark *):hover{color:color-mix(in oklab,var(--gray-300) 75%,transparent)}}}.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn-icon:where(.dark,.dark *):focus-visible{color:var(--gray-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])) .fi-badge-delete-btn-icon:where(.dark,.dark *):focus-visible{color:color-mix(in oklab,var(--gray-300) 75%,transparent)}}@media (hover:hover){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn-icon:hover{color:var(--color-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn-icon:hover{color:color-mix(in oklab,var(--color-700) 75%,transparent)}}}.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn-icon:focus-visible{color:var(--color-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn-icon:focus-visible{color:color-mix(in oklab,var(--color-700) 75%,transparent)}}@media (hover:hover){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn-icon:where(.dark,.dark *):hover{color:var(--color-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn-icon:where(.dark,.dark *):hover{color:color-mix(in oklab,var(--color-300) 75%,transparent)}}}.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn-icon:where(.dark,.dark *):focus-visible{color:var(--color-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-badge.fi-force-enabled,.fi-badge:not(.fi-disabled):not([disabled])).fi-color .fi-badge-delete-btn-icon:where(.dark,.dark *):focus-visible{color:color-mix(in oklab,var(--color-300) 75%,transparent)}}.custom-fields-component .fi-breadcrumbs ol{align-items:center;column-gap:calc(var(--spacing)*2);flex-wrap:wrap;display:flex}.custom-fields-component .fi-breadcrumbs ol li{align-items:center;column-gap:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-500);display:flex}.custom-fields-component .fi-breadcrumbs ol li:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-breadcrumbs ol li a{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-breadcrumbs ol li a:hover{color:var(--gray-700)}.custom-fields-component .fi-breadcrumbs ol li a:where(.dark,.dark *):hover{color:var(--gray-200)}}.custom-fields-component .fi-breadcrumbs ol li .fi-icon{color:var(--gray-400);display:flex}.custom-fields-component .fi-breadcrumbs ol li .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-breadcrumbs ol li .fi-icon.fi-ltr:where(:dir(rtl),[dir=rtl],[dir=rtl] *),.custom-fields-component .fi-breadcrumbs ol li .fi-icon.fi-rtl:where(:dir(ltr),[dir=ltr],[dir=ltr] *){display:none}.custom-fields-component .fi-btn{justify-content:center;align-items:center;gap:calc(var(--spacing)*1.5);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);vertical-align:middle;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;--tw-outline-style:none;outline-style:none;grid-auto-flow:column;transition-duration:75ms;display:inline-grid;position:relative}.custom-fields-component :is(.fi-btn.fi-force-enabled,.fi-btn:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-btn.fi-disabled:not(.fi-force-enabled),.custom-fields-component .fi-btn[disabled]:not(.fi-force-enabled){cursor:default;opacity:.7}.custom-fields-component :is(.fi-btn.fi-disabled:not(.fi-force-enabled),.fi-btn[disabled]:not(.fi-force-enabled)):not([x-tooltip]){pointer-events:none}.custom-fields-component .fi-btn>.fi-icon{color:var(--gray-400);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-btn>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-btn.fi-size-xs{gap:var(--spacing);padding-inline:calc(var(--spacing)*2);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-btn.fi-size-sm{gap:var(--spacing);padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-btn.fi-size-lg{padding-inline:calc(var(--spacing)*3.5);padding-block:calc(var(--spacing)*2.5)}.custom-fields-component .fi-btn.fi-size-lg,.custom-fields-component .fi-btn.fi-size-xl{gap:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-btn.fi-size-xl{padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3)}.custom-fields-component .fi-btn.fi-outlined{color:var(--gray-950);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-300)}.custom-fields-component .fi-btn.fi-outlined:where(.dark,.dark *){color:var(--color-white);--tw-ring-color:var(--gray-700)}@media (hover:hover){.custom-fields-component :is(.fi-btn.fi-outlined.fi-force-enabled,.fi-btn.fi-outlined:not(.fi-disabled):not([disabled])):hover{background-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-force-enabled,.fi-btn.fi-outlined:not(.fi-disabled):not([disabled])):hover{background-color:color-mix(in oklab,var(--gray-400) 10%,transparent)}}}.custom-fields-component :is(.fi-btn.fi-outlined.fi-force-enabled,.fi-btn.fi-outlined:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-force-enabled,.fi-btn.fi-outlined:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:color-mix(in oklab, var(--gray-400) 40%, transparent)}}.custom-fields-component .fi-btn.fi-outlined.fi-color{color:var(--text);--tw-ring-color:var(--color-600)}.custom-fields-component .fi-btn.fi-outlined.fi-color:where(.dark,.dark *){color:var(--dark-text);--tw-ring-color:var(--color-500)}@media (hover:hover){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):hover{background-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):hover{background-color:color-mix(in oklab,var(--color-500) 10%,transparent)}}}.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:color-mix(in oklab, var(--color-500) 40%, transparent)}}@media (hover:hover){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:var(--color-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-600) 10%,transparent)}}}.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn.fi-outlined.fi-color.fi-force-enabled,.fi-btn.fi-outlined.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:color-mix(in oklab, var(--color-400) 40%, transparent)}}.custom-fields-component .fi-btn.fi-outlined.fi-color>.fi-icon{color:var(--color-600)}.custom-fields-component .fi-btn.fi-outlined.fi-color>.fi-icon:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-btn:not(.fi-outlined){background-color:var(--color-white);color:var(--gray-950)}.custom-fields-component .fi-btn:not(.fi-outlined):where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-btn:not(.fi-outlined):where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-btn:not(.fi-outlined):where(.dark,.dark *){color:var(--color-white)}@media (hover:hover){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-force-enabled,.fi-btn:not(.fi-outlined):not(.fi-disabled):not([disabled])):hover{background-color:var(--gray-50)}.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-force-enabled,.fi-btn:not(.fi-outlined):not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-force-enabled,.fi-btn:not(.fi-outlined):not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}}.custom-fields-component input:checked+label.fi-btn:not(.fi-outlined){background-color:var(--gray-400);color:var(--color-white);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component input:checked+label.fi-btn:not(.fi-outlined):where(.dark,.dark *){background-color:var(--gray-600)}@media (hover:hover){.custom-fields-component :is(input:checked+label.fi-btn:not(.fi-outlined).fi-force-enabled,input:checked+label.fi-btn:not(.fi-outlined):not(.fi-disabled):not([disabled])):hover{background-color:var(--gray-300)}.custom-fields-component :is(input:checked+label.fi-btn:not(.fi-outlined).fi-force-enabled,input:checked+label.fi-btn:not(.fi-outlined):not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:var(--gray-500)}}.custom-fields-component .fi-btn:not(.fi-outlined).fi-color:not(label){background-color:var(--bg);color:var(--text)}.custom-fields-component .fi-btn:not(.fi-outlined).fi-color:not(label):where(.dark,.dark *){background-color:var(--dark-bg);color:var(--dark-text)}@media (hover:hover){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):hover{background-color:var(--hover-bg);color:var(--hover-text)}}.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:color-mix(in oklab, var(--color-500) 50%, transparent)}}@media (hover:hover){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:var(--dark-hover-bg);color:var(--dark-hover-text)}}.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn:not(.fi-outlined).fi-color:not(label).fi-force-enabled,.fi-btn:not(.fi-outlined).fi-color:not(label):not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:color-mix(in oklab, var(--color-400) 50%, transparent)}}.custom-fields-component .fi-btn:not(.fi-outlined).fi-color:not(label)>.fi-icon{color:var(--text)}.custom-fields-component .fi-btn:not(.fi-outlined).fi-color:not(label)>.fi-icon:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component input:checked+:is(label.fi-btn:not(.fi-outlined).fi-color){background-color:var(--bg);color:var(--text);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component input:checked+:is(label.fi-btn:not(.fi-outlined).fi-color):where(.dark,.dark *){background-color:var(--dark-bg);color:var(--dark-text)}@media (hover:hover){.custom-fields-component input:checked+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):hover{background-color:var(--hover-bg);color:var(--hover-text)}.custom-fields-component input:checked+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{background-color:var(--dark-hover-bg);color:var(--dark-hover-text)}}.custom-fields-component input:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])){z-index:10;--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])){--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component input:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component input:checked:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])){--tw-ring-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input:checked:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])){--tw-ring-color:color-mix(in oklab, var(--color-500) 50%, transparent)}}.custom-fields-component input:checked:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *){--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input:checked:focus-visible+:is(label.fi-btn:not(.fi-outlined).fi-color.fi-force-enabled,label.fi-btn:not(.fi-outlined).fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-400) 50%, transparent)}}.custom-fields-component label.fi-btn{cursor:pointer}.custom-fields-component label.fi-btn>.fi-icon:is(:checked+label>.fi-icon){color:var(--text)}.custom-fields-component label.fi-btn>.fi-icon:is(:checked+label>.fi-icon):where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-btn:not(.fi-color),.custom-fields-component label.fi-btn{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-btn:not(.fi-color),.custom-fields-component label.fi-btn{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component :is(.fi-btn:not(.fi-color),label.fi-btn):where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-btn:not(.fi-color),label.fi-btn):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-btn.fi-labeled-from-2xl,.custom-fields-component .fi-btn.fi-labeled-from-lg,.custom-fields-component .fi-btn.fi-labeled-from-md,.custom-fields-component .fi-btn.fi-labeled-from-sm,.custom-fields-component .fi-btn.fi-labeled-from-xl{display:none}@media (min-width:40rem){.custom-fields-component .fi-btn.fi-labeled-from-sm{display:inline-grid}}@media (min-width:48rem){.custom-fields-component .fi-btn.fi-labeled-from-md{display:inline-grid}}@media (min-width:64rem){.custom-fields-component .fi-btn.fi-labeled-from-lg{display:inline-grid}}@media (min-width:80rem){.custom-fields-component .fi-btn.fi-labeled-from-xl{display:inline-grid}}@media (min-width:96rem){.custom-fields-component .fi-btn.fi-labeled-from-2xl{display:inline-grid}}.custom-fields-component .fi-btn .fi-btn-badge-ctn{z-index:1;--tw-translate-x:-50%;width:max-content;--tw-translate-y:-50%;translate:var(--tw-translate-x) var(--tw-translate-y);border-radius:var(--radius-md);background-color:var(--color-white);inset-inline-start:100%;display:flex;position:absolute;top:0}.custom-fields-component .fi-btn .fi-btn-badge-ctn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:50%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-btn .fi-btn-badge-ctn:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-btn-group{border-radius:var(--radius-lg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);grid-auto-flow:column;display:grid}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-btn-group{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-btn-group:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-btn-group:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-btn-group>.fi-btn{border-radius:0;flex:1}.custom-fields-component .fi-btn-group>.fi-btn:nth-child(1 of .fi-btn){border-start-start-radius:var(--radius-lg);border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-btn-group>.fi-btn:nth-last-child(1 of .fi-btn){border-start-end-radius:var(--radius-lg);border-end-end-radius:var(--radius-lg)}.custom-fields-component .fi-btn-group>.fi-btn:not(:nth-child(1 of .fi-btn)){--tw-shadow:-1px 0 0 0 var(--tw-shadow-color,var(--color-gray-200));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-btn-group>.fi-btn:not(:nth-child(1 of .fi-btn)):where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-shadow:1px 0 0 0 var(--tw-shadow-color,var(--color-gray-200));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-btn-group>.fi-btn:not(:nth-child(1 of .fi-btn)):where(.dark,.dark *){--tw-shadow:-1px 0 0 0 var(--tw-shadow-color,#fff3);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-btn-group>.fi-btn:not(:nth-child(1 of .fi-btn)):where(:dir(rtl),[dir=rtl],[dir=rtl] *):where(.dark,.dark *){--tw-shadow:1px 0 0 0 var(--tw-shadow-color,#fff3);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-btn-group>.fi-btn:not(:nth-last-child(1 of .fi-btn)){margin-inline-end:1px}.custom-fields-component .fi-btn-group>.fi-btn.fi-processing:enabled{cursor:wait;opacity:.7}.custom-fields-component .fi-btn-group>.fi-btn:not(.fi-outlined){--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-btn-group>.fi-btn:not(.fi-color),.custom-fields-component label:is(.fi-btn-group>.fi-btn){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-callout{gap:calc(var(--spacing)*3);border-radius:var(--radius-xl);background-color:var(--color-white);width:100%;padding:calc(var(--spacing)*4);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);display:flex}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-callout{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-callout:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-callout:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-callout .fi-callout-icon{color:var(--gray-400)}.custom-fields-component .fi-callout .fi-callout-icon.fi-color{color:var(--color-400)}.custom-fields-component .fi-callout .fi-callout-main{margin-top:calc(var(--spacing)*.5);gap:calc(var(--spacing)*3);flex:1;display:grid}.custom-fields-component .fi-callout .fi-callout-text{gap:var(--spacing);display:grid}.custom-fields-component .fi-callout .fi-callout-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-callout .fi-callout-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-callout .fi-callout-description{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));text-wrap:pretty;overflow-wrap:break-word;color:var(--gray-500);overflow:hidden}.custom-fields-component .fi-callout .fi-callout-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-callout .fi-callout-description>p:not(:first-of-type){margin-top:var(--spacing)}.custom-fields-component .fi-callout .fi-callout-footer{gap:calc(var(--spacing)*3);flex-wrap:wrap;display:flex}.custom-fields-component .fi-callout .fi-callout-controls{align-self:flex-start}.custom-fields-component .fi-callout.fi-color{--tw-ring-color:var(--color-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-callout.fi-color{--tw-ring-color:color-mix(in oklab, var(--color-600) 20%, transparent)}}.custom-fields-component .fi-callout.fi-color:where(.dark,.dark *){--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-callout.fi-color:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-400) 30%, transparent)}}.custom-fields-component .fi-callout.fi-color{background-color:#fff}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-callout.fi-color{background-color:color-mix(in oklab,#fff 90%,var(--color-400))}}.custom-fields-component .fi-callout.fi-color:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-callout.fi-color:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900) 90%,var(--color-400))}}.custom-fields-component .fi-callout.fi-color .fi-callout-description{color:var(--gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-callout.fi-color .fi-callout-description{color:color-mix(in oklab,var(--gray-700) 75%,transparent)}}.custom-fields-component .fi-callout.fi-color .fi-callout-description:where(.dark,.dark *){color:var(--gray-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-callout.fi-color .fi-callout-description:where(.dark,.dark *){color:color-mix(in oklab,var(--gray-300) 75%,transparent)}}.custom-fields-component .fi-dropdown-header{gap:calc(var(--spacing)*2);width:100%;padding:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:flex}.custom-fields-component .fi-dropdown-header .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-dropdown-header .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-dropdown-header span{text-overflow:ellipsis;white-space:nowrap;text-align:start;color:var(--gray-700);flex:1;overflow:hidden}.custom-fields-component .fi-dropdown-header span:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-dropdown-header.fi-color .fi-icon{color:var(--color-500)}.custom-fields-component .fi-dropdown-header.fi-color .fi-icon:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-dropdown-header.fi-color span{color:var(--text)}.custom-fields-component .fi-dropdown-header.fi-color span:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component :scope .fi-dropdown-trigger{cursor:pointer;display:flex}.custom-fields-component :scope .fi-dropdown-panel{z-index:20;border-radius:var(--radius-lg);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);width:100vw;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);position:absolute;max-width:14rem!important}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :scope .fi-dropdown-panel{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component :scope .fi-dropdown-panel{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.custom-fields-component :scope .fi-dropdown-panel:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :scope .fi-dropdown-panel:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component :where(:scope .fi-dropdown-panel:not(.fi-dropdown-list)>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-100)}.custom-fields-component :where(:scope .fi-dropdown-panel:not(.fi-dropdown-list):where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(:scope .fi-dropdown-panel:not(.fi-dropdown-list):where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component :scope .fi-dropdown-panel.fi-opacity-0{opacity:0}.custom-fields-component :scope .fi-dropdown-panel.fi-width-3xs{max-width:var(--container-3xs)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-2xs{max-width:var(--container-2xs)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-xs{max-width:var(--container-xs)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-sm{max-width:var(--container-sm)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-md{max-width:var(--container-md)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-lg{max-width:var(--container-lg)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-xl{max-width:var(--container-xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-2xl{max-width:var(--container-2xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-3xl{max-width:var(--container-3xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-4xl{max-width:var(--container-4xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-5xl{max-width:var(--container-5xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-6xl{max-width:var(--container-6xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-7xl{max-width:var(--container-7xl)!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-none{max-width:none!important}.custom-fields-component :scope .fi-dropdown-panel.fi-width-container{width:100%!important}@media (min-width:40rem){.custom-fields-component :scope .fi-dropdown-panel.fi-width-container{max-width:40rem!important}}@media (min-width:48rem){.custom-fields-component :scope .fi-dropdown-panel.fi-width-container{max-width:48rem!important}}@media (min-width:64rem){.custom-fields-component :scope .fi-dropdown-panel.fi-width-container{max-width:64rem!important}}@media (min-width:80rem){.custom-fields-component :scope .fi-dropdown-panel.fi-width-container{max-width:80rem!important}}@media (min-width:96rem){.custom-fields-component :scope .fi-dropdown-panel.fi-width-container{max-width:96rem!important}}.custom-fields-component :scope .fi-dropdown-panel.fi-scrollable{overflow-y:auto}.custom-fields-component .fi-dropdown-list{padding:var(--spacing);gap:1px;display:grid}.custom-fields-component .fi-dropdown-list>.fi-grid{overflow-x:hidden}.custom-fields-component .fi-dropdown-list-item{align-items:center;gap:calc(var(--spacing)*2);border-radius:var(--radius-md);width:100%;padding:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));white-space:nowrap;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;--tw-outline-style:none;-webkit-user-select:none;user-select:none;outline-style:none;transition-duration:75ms;display:flex;overflow:hidden}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):hover{background-color:var(--gray-50)}}.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):focus-visible{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]):where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]).fi-selected{background-color:var(--gray-50)}.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]).fi-selected:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item:not(.fi-disabled):not([disabled]).fi-selected:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-dropdown-list-item.fi-disabled,.custom-fields-component .fi-dropdown-list-item[disabled]{cursor:default;opacity:.7}.custom-fields-component :is(.fi-dropdown-list-item.fi-disabled,.fi-dropdown-list-item[disabled]):not([x-tooltip]){pointer-events:none}.custom-fields-component .fi-dropdown-list-item .fi-icon{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-dropdown-list-item .fi-dropdown-list-item-image{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);background-position:50%;background-size:cover;border-radius:3.40282e+38px}.custom-fields-component .fi-dropdown-list-item>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-dropdown-list-item>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-dropdown-list-item>.fi-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-dropdown-list-item>.fi-icon.fi-color:where(.dark,.dark *){color:var(--color-400)}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):hover{background-color:var(--color-50)}}.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):focus-visible{background-color:var(--color-50)}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):where(.dark,.dark *):hover{background-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-400) 10%,transparent)}}}.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):where(.dark,.dark *):focus-visible{background-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]):where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-400) 10%,transparent)}}.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]).fi-selected{background-color:var(--color-50)}.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]).fi-selected:where(.dark,.dark *){background-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-dropdown-list-item.fi-color:not(.fi-disabled):not([disabled]).fi-selected:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-400) 10%,transparent)}}.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label{color:var(--text)}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label:hover{color:var(--hover-text)}}.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label:where(.dark,.dark *){color:var(--dark-text)}@media (hover:hover){.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label:where(.dark,.dark *):hover{color:var(--dark-hover-text)}}.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label.fi-selected{color:var(--hover-text)}.custom-fields-component .fi-dropdown-list-item.fi-color .fi-dropdown-list-item-label.fi-selected:where(.dark,.dark *){color:var(--dark-hover-text)}.custom-fields-component .fi-dropdown-list-item .fi-badge{min-width:1.25rem;padding-inline:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*.5);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.custom-fields-component .fi-dropdown-list-item-label{text-overflow:ellipsis;white-space:nowrap;text-align:start;color:var(--gray-700);flex:1;overflow:hidden}.custom-fields-component .fi-dropdown-list-item-label:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-dropdown-list-item-badge-placeholder{color:var(--gray-400);align-items:center;display:flex}.custom-fields-component .fi-dropdown-list-item-badge-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-empty-state{padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*12)}.custom-fields-component .fi-empty-state:not(.fi-empty-state-not-contained){border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-empty-state:not(.fi-empty-state-not-contained){--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-empty-state:not(.fi-empty-state-not-contained):where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-empty-state:not(.fi-empty-state-not-contained):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-empty-state .fi-empty-state-content{max-width:var(--container-lg);text-align:center;justify-items:center;margin-inline:auto;display:grid}.custom-fields-component .fi-empty-state .fi-empty-state-text-ctn{text-align:center;justify-items:center;display:grid}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg{margin-bottom:calc(var(--spacing)*4);background-color:var(--gray-100);padding:calc(var(--spacing)*3);border-radius:3.40282e+38px}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg:where(.dark,.dark *){background-color:var(--gray-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-500) 20%,transparent)}}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg.fi-color{background-color:var(--color-100)}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg.fi-color:where(.dark,.dark *){background-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg.fi-color:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-500) 20%,transparent)}}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg .fi-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-empty-state .fi-empty-state-icon-bg .fi-icon.fi-color:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-empty-state .fi-empty-state-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}.custom-fields-component .fi-empty-state .fi-empty-state-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-empty-state .fi-empty-state-description{margin-top:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-empty-state .fi-empty-state-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-empty-state .fi-empty-state-footer{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-empty-state.fi-compact{padding-block:calc(var(--spacing)*6)}.custom-fields-component .fi-empty-state.fi-compact .fi-empty-state-content{align-items:flex-start;gap:calc(var(--spacing)*4);text-align:start;max-width:none;margin-inline:0;display:flex}.custom-fields-component .fi-empty-state.fi-compact .fi-empty-state-icon-bg{flex-shrink:0;margin-bottom:0}.custom-fields-component .fi-empty-state.fi-compact .fi-empty-state-text-ctn{text-align:start;flex:1;justify-items:start}.custom-fields-component .fi-empty-state.fi-compact .fi-empty-state-description{margin-top:var(--spacing)}.custom-fields-component .fi-empty-state.fi-compact .fi-empty-state-footer{margin-top:calc(var(--spacing)*4)}.custom-fields-component .fi-fieldset>legend{padding-inline:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950);margin-inline-start:calc(var(--spacing)*-2)}.custom-fields-component .fi-fieldset>legend:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fieldset>legend .fi-fieldset-label-required-mark{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--danger-600)}.custom-fields-component .fi-fieldset>legend .fi-fieldset-label-required-mark:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-fieldset.fi-fieldset-label-hidden>legend{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.custom-fields-component .fi-fieldset:not(.fi-fieldset-not-contained){border-radius:var(--radius-xl);border-style:var(--tw-border-style);border-width:1px;border-color:var(--gray-200);padding:calc(var(--spacing)*6)}.custom-fields-component .fi-fieldset:not(.fi-fieldset-not-contained):where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fieldset:not(.fi-fieldset-not-contained):where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fieldset.fi-fieldset-not-contained{padding-top:calc(var(--spacing)*6)}.custom-fields-component .fi-grid:not(.fi-grid-direction-col){grid-template-columns:var(--cols-default);display:grid}@media (min-width:40rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).sm\:fi-grid-cols{grid-template-columns:var(--cols-sm)}}@media (min-width:48rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).md\:fi-grid-cols{grid-template-columns:var(--cols-md)}}@media (min-width:64rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).lg\:fi-grid-cols{grid-template-columns:var(--cols-lg)}}@media (min-width:80rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).xl\:fi-grid-cols{grid-template-columns:var(--cols-xl)}}@media (min-width:96rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\32 xl\:fi-grid-cols{grid-template-columns:var(--cols-2xl)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@3xs\:fi-grid-cols{grid-template-columns:var(--cols-c3xs)}}@container (min-width:18rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@2xs\:fi-grid-cols{grid-template-columns:var(--cols-c2xs)}}@container (min-width:20rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@xs\:fi-grid-cols{grid-template-columns:var(--cols-cxs)}}@container (min-width:24rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@sm\:fi-grid-cols{grid-template-columns:var(--cols-csm)}}@container (min-width:28rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@md\:fi-grid-cols{grid-template-columns:var(--cols-cmd)}}@container (min-width:32rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@lg\:fi-grid-cols{grid-template-columns:var(--cols-clg)}}@container (min-width:36rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@xl\:fi-grid-cols{grid-template-columns:var(--cols-cxl)}}@container (min-width:42rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@2xl\:fi-grid-cols{grid-template-columns:var(--cols-c2xl)}}@container (min-width:48rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@3xl\:fi-grid-cols{grid-template-columns:var(--cols-c3xl)}}@container (min-width:56rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@4xl\:fi-grid-cols{grid-template-columns:var(--cols-c4xl)}}@container (min-width:64rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@5xl\:fi-grid-cols{grid-template-columns:var(--cols-c5xl)}}@container (min-width:72rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@6xl\:fi-grid-cols{grid-template-columns:var(--cols-c6xl)}}@container (min-width:80rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\@7xl\:fi-grid-cols{grid-template-columns:var(--cols-c7xl)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\!\@sm\:fi-grid-cols{grid-template-columns:var(--cols-ncsm)}}@media (min-width:48rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\!\@md\:fi-grid-cols{grid-template-columns:var(--cols-ncmd)}}@media (min-width:64rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\!\@lg\:fi-grid-cols{grid-template-columns:var(--cols-nclg)}}@media (min-width:80rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\!\@xl\:fi-grid-cols{grid-template-columns:var(--cols-ncxl)}}@media (min-width:96rem){.custom-fields-component .fi-grid:not(.fi-grid-direction-col).\!\@2xl\:fi-grid-cols{grid-template-columns:var(--cols-nc2xl)}}}.custom-fields-component .fi-grid.fi-grid-direction-col{columns:var(--cols-default)}@media (min-width:40rem){.custom-fields-component .fi-grid.fi-grid-direction-col.sm\:fi-grid-cols{columns:var(--cols-sm)}}@media (min-width:48rem){.custom-fields-component .fi-grid.fi-grid-direction-col.md\:fi-grid-cols{columns:var(--cols-md)}}@media (min-width:64rem){.custom-fields-component .fi-grid.fi-grid-direction-col.lg\:fi-grid-cols{columns:var(--cols-lg)}}@media (min-width:80rem){.custom-fields-component .fi-grid.fi-grid-direction-col.xl\:fi-grid-cols{columns:var(--cols-xl)}}@media (min-width:96rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\32 xl\:fi-grid-cols{columns:var(--cols-2xl)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@3xs\:fi-grid-cols{columns:var(--cols-c3xs)}}@container (min-width:18rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@2xs\:fi-grid-cols{columns:var(--cols-c2xs)}}@container (min-width:20rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@xs\:fi-grid-cols{columns:var(--cols-cxs)}}@container (min-width:24rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@sm\:fi-grid-cols{columns:var(--cols-csm)}}@container (min-width:28rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@md\:fi-grid-cols{columns:var(--cols-cmd)}}@container (min-width:32rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@lg\:fi-grid-cols{columns:var(--cols-clg)}}@container (min-width:36rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@xl\:fi-grid-cols{columns:var(--cols-cxl)}}@container (min-width:42rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@2xl\:fi-grid-cols{columns:var(--cols-c2xl)}}@container (min-width:48rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@3xl\:fi-grid-cols{columns:var(--cols-c3xl)}}@container (min-width:56rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@4xl\:fi-grid-cols{columns:var(--cols-c4xl)}}@container (min-width:64rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@5xl\:fi-grid-cols{columns:var(--cols-c5xl)}}@container (min-width:72rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@6xl\:fi-grid-cols{columns:var(--cols-c6xl)}}@container (min-width:80rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\@7xl\:fi-grid-cols{columns:var(--cols-c7xl)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\!\@sm\:fi-grid-cols{columns:var(--cols-ncsm)}}@media (min-width:48rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\!\@md\:fi-grid-cols{columns:var(--cols-ncmd)}}@media (min-width:64rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\!\@lg\:fi-grid-cols{columns:var(--cols-nclg)}}@media (min-width:80rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\!\@xl\:fi-grid-cols{columns:var(--cols-ncxl)}}@media (min-width:96rem){.custom-fields-component .fi-grid.fi-grid-direction-col.\!\@2xl\:fi-grid-cols{columns:var(--cols-nc2xl)}}}@supports (container-type:inline-size){.custom-fields-component .fi-grid-ctn{container-type:inline-size}}.custom-fields-component .fi-grid-col{grid-column:var(--col-span-default)}@media (min-width:40rem){.custom-fields-component .fi-grid-col.sm\:fi-grid-col-span{grid-column:var(--col-span-sm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.md\:fi-grid-col-span{grid-column:var(--col-span-md)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.lg\:fi-grid-col-span{grid-column:var(--col-span-lg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.xl\:fi-grid-col-span{grid-column:var(--col-span-xl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\32 xl\:fi-grid-col-span{grid-column:var(--col-span-2xl)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component .fi-grid-col.\@3xs\:fi-grid-col-span{grid-column:var(--col-span-c3xs)}}@container (min-width:18rem){.custom-fields-component .fi-grid-col.\@2xs\:fi-grid-col-span{grid-column:var(--col-span-c2xs)}}@container (min-width:20rem){.custom-fields-component .fi-grid-col.\@xs\:fi-grid-col-span{grid-column:var(--col-span-cxs)}}@container (min-width:24rem){.custom-fields-component .fi-grid-col.\@sm\:fi-grid-col-span{grid-column:var(--col-span-csm)}}@container (min-width:28rem){.custom-fields-component .fi-grid-col.\@md\:fi-grid-col-span{grid-column:var(--col-span-cmd)}}@container (min-width:32rem){.custom-fields-component .fi-grid-col.\@lg\:fi-grid-col-span{grid-column:var(--col-span-clg)}}@container (min-width:36rem){.custom-fields-component .fi-grid-col.\@xl\:fi-grid-col-span{grid-column:var(--col-span-cxl)}}@container (min-width:42rem){.custom-fields-component .fi-grid-col.\@2xl\:fi-grid-col-span{grid-column:var(--col-span-c2xl)}}@container (min-width:48rem){.custom-fields-component .fi-grid-col.\@3xl\:fi-grid-col-span{grid-column:var(--col-span-c3xl)}}@container (min-width:56rem){.custom-fields-component .fi-grid-col.\@4xl\:fi-grid-col-span{grid-column:var(--col-span-c4xl)}}@container (min-width:64rem){.custom-fields-component .fi-grid-col.\@5xl\:fi-grid-col-span{grid-column:var(--col-span-c5xl)}}@container (min-width:72rem){.custom-fields-component .fi-grid-col.\@6xl\:fi-grid-col-span{grid-column:var(--col-span-c6xl)}}@container (min-width:80rem){.custom-fields-component .fi-grid-col.\@7xl\:fi-grid-col-span{grid-column:var(--col-span-c7xl)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-grid-col.\!\@sm\:fi-grid-col-span{grid-column:var(--col-span-ncsm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.\!\@md\:fi-grid-col-span{grid-column:var(--col-span-ncmd)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.\!\@lg\:fi-grid-col-span{grid-column:var(--col-span-nclg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.\!\@xl\:fi-grid-col-span{grid-column:var(--col-span-ncxl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\!\@2xl\:fi-grid-col-span{grid-column:var(--col-span-nc2xl)}}}.custom-fields-component .fi-grid-col.fi-grid-col-start{grid-column-start:var(--col-start-default)}@media (min-width:40rem){.custom-fields-component .fi-grid-col.sm\:fi-grid-col-start{grid-column-start:var(--col-start-sm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.md\:fi-grid-col-start{grid-column-start:var(--col-start-md)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.lg\:fi-grid-col-start{grid-column-start:var(--col-start-lg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.xl\:fi-grid-col-start{grid-column-start:var(--col-start-xl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\32 xl\:fi-grid-col-start{grid-column-start:var(--col-start-2xl)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component .fi-grid-col.\@3xs\:fi-grid-col-start{grid-column-start:var(--col-start-c3xs)}}@container (min-width:18rem){.custom-fields-component .fi-grid-col.\@2xs\:fi-grid-col-start{grid-column-start:var(--col-start-c2xs)}}@container (min-width:20rem){.custom-fields-component .fi-grid-col.\@xs\:fi-grid-col-start{grid-column-start:var(--col-start-cxs)}}@container (min-width:24rem){.custom-fields-component .fi-grid-col.\@sm\:fi-grid-col-start{grid-column-start:var(--col-start-csm)}}@container (min-width:28rem){.custom-fields-component .fi-grid-col.\@md\:fi-grid-col-start{grid-column-start:var(--col-start-cmd)}}@container (min-width:32rem){.custom-fields-component .fi-grid-col.\@lg\:fi-grid-col-start{grid-column-start:var(--col-start-clg)}}@container (min-width:36rem){.custom-fields-component .fi-grid-col.\@xl\:fi-grid-col-start{grid-column-start:var(--col-start-cxl)}}@container (min-width:42rem){.custom-fields-component .fi-grid-col.\@2xl\:fi-grid-col-start{grid-column-start:var(--col-start-c2xl)}}@container (min-width:48rem){.custom-fields-component .fi-grid-col.\@3xl\:fi-grid-col-start{grid-column-start:var(--col-start-c3xl)}}@container (min-width:56rem){.custom-fields-component .fi-grid-col.\@4xl\:fi-grid-col-start{grid-column-start:var(--col-start-c4xl)}}@container (min-width:64rem){.custom-fields-component .fi-grid-col.\@5xl\:fi-grid-col-start{grid-column-start:var(--col-start-c5xl)}}@container (min-width:72rem){.custom-fields-component .fi-grid-col.\@6xl\:fi-grid-col-start{grid-column-start:var(--col-start-c6xl)}}@container (min-width:80rem){.custom-fields-component .fi-grid-col.\@7xl\:fi-grid-col-start{grid-column-start:var(--col-start-c7xl)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-grid-col.\!\@sm\:fi-grid-col-start{grid-column-start:var(--col-start-ncsm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.\!\@md\:fi-grid-col-start{grid-column-start:var(--col-start-ncmd)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.\!\@lg\:fi-grid-col-start{grid-column-start:var(--col-start-nclg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.\!\@xl\:fi-grid-col-start{grid-column-start:var(--col-start-ncxl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\!\@2xl\:fi-grid-col-start{grid-column-start:var(--col-start-nc2xl)}}}.custom-fields-component .fi-grid-col.fi-grid-col-order{order:var(--col-order-default)}@media (min-width:40rem){.custom-fields-component .fi-grid-col.sm\:fi-grid-col-order{order:var(--col-order-sm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.md\:fi-grid-col-order{order:var(--col-order-md)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.lg\:fi-grid-col-order{order:var(--col-order-lg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.xl\:fi-grid-col-order{order:var(--col-order-xl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\32 xl\:fi-grid-col-order{order:var(--col-order-2xl)}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component .fi-grid-col.\@3xs\:fi-grid-col-order{order:var(--col-order-c3xs)}}@container (min-width:18rem){.custom-fields-component .fi-grid-col.\@2xs\:fi-grid-col-order{order:var(--col-order-c2xs)}}@container (min-width:20rem){.custom-fields-component .fi-grid-col.\@xs\:fi-grid-col-order{order:var(--col-order-cxs)}}@container (min-width:24rem){.custom-fields-component .fi-grid-col.\@sm\:fi-grid-col-order{order:var(--col-order-csm)}}@container (min-width:28rem){.custom-fields-component .fi-grid-col.\@md\:fi-grid-col-order{order:var(--col-order-cmd)}}@container (min-width:32rem){.custom-fields-component .fi-grid-col.\@lg\:fi-grid-col-order{order:var(--col-order-clg)}}@container (min-width:36rem){.custom-fields-component .fi-grid-col.\@xl\:fi-grid-col-order{order:var(--col-order-cxl)}}@container (min-width:42rem){.custom-fields-component .fi-grid-col.\@2xl\:fi-grid-col-order{order:var(--col-order-c2xl)}}@container (min-width:48rem){.custom-fields-component .fi-grid-col.\@3xl\:fi-grid-col-order{order:var(--col-order-c3xl)}}@container (min-width:56rem){.custom-fields-component .fi-grid-col.\@4xl\:fi-grid-col-order{order:var(--col-order-c4xl)}}@container (min-width:64rem){.custom-fields-component .fi-grid-col.\@5xl\:fi-grid-col-order{order:var(--col-order-c5xl)}}@container (min-width:72rem){.custom-fields-component .fi-grid-col.\@6xl\:fi-grid-col-order{order:var(--col-order-c6xl)}}@container (min-width:80rem){.custom-fields-component .fi-grid-col.\@7xl\:fi-grid-col-order{order:var(--col-order-c7xl)}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-grid-col.\!\@sm\:fi-grid-col-order{order:var(--col-order-ncsm)}}@media (min-width:48rem){.custom-fields-component .fi-grid-col.\!\@md\:fi-grid-col-order{order:var(--col-order-ncmd)}}@media (min-width:64rem){.custom-fields-component .fi-grid-col.\!\@lg\:fi-grid-col-order{order:var(--col-order-nclg)}}@media (min-width:80rem){.custom-fields-component .fi-grid-col.\!\@xl\:fi-grid-col-order{order:var(--col-order-ncxl)}}@media (min-width:96rem){.custom-fields-component .fi-grid-col.\!\@2xl\:fi-grid-col-order{order:var(--col-order-nc2xl)}}}.custom-fields-component .fi-grid-col.fi-hidden{display:none}.custom-fields-component .fi-icon{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.custom-fields-component .fi-icon.fi-size-xs{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.custom-fields-component .fi-icon.fi-size-sm{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.custom-fields-component .fi-icon.fi-size-md{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.custom-fields-component .fi-icon.fi-size-lg{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.custom-fields-component .fi-icon.fi-size-xl{width:calc(var(--spacing)*7);height:calc(var(--spacing)*7)}.custom-fields-component .fi-icon.fi-size-2xl{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8)}.custom-fields-component .fi-icon>svg{height:inherit;width:inherit}.custom-fields-component .fi-icon-btn{margin:calc(var(--spacing)*-2);width:calc(var(--spacing)*9);height:calc(var(--spacing)*9);border-radius:var(--radius-lg);color:var(--gray-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;--tw-outline-style:none;outline-style:none;justify-content:center;align-items:center;transition-duration:75ms;display:flex;position:relative}.custom-fields-component .fi-icon-btn:where(.dark,.dark *){color:var(--gray-500)}@media (hover:hover){.custom-fields-component :is(.fi-icon-btn.fi-force-enabled,.fi-icon-btn:not(.fi-disabled):not([disabled])):hover{color:var(--gray-600)}}.custom-fields-component :is(.fi-icon-btn.fi-force-enabled,.fi-icon-btn:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}@media (hover:hover){.custom-fields-component :is(.fi-icon-btn.fi-force-enabled,.fi-icon-btn:not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{color:var(--gray-400)}}.custom-fields-component :is(.fi-icon-btn.fi-force-enabled,.fi-icon-btn:not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-icon-btn.fi-disabled:not(.fi-force-enabled),.custom-fields-component .fi-icon-btn[disabled]:not(.fi-force-enabled){cursor:default;opacity:.7}.custom-fields-component :is(.fi-icon-btn.fi-disabled:not(.fi-force-enabled),.fi-icon-btn[disabled]:not(.fi-force-enabled)):not([x-tooltip]){pointer-events:none}.custom-fields-component .fi-icon-btn.fi-size-xs{width:calc(var(--spacing)*7);height:calc(var(--spacing)*7)}.custom-fields-component .fi-icon-btn.fi-size-xs:has(.fi-icon.fi-size-sm){margin:calc(var(--spacing)*-1.5)}.custom-fields-component .fi-icon-btn.fi-size-xs:has(.fi-icon.fi-size-md){margin:calc(var(--spacing)*-1)}.custom-fields-component .fi-icon-btn.fi-size-xs:has(.fi-icon.fi-size-lg){margin:calc(var(--spacing)*-.5)}.custom-fields-component .fi-icon-btn.fi-size-sm{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8)}.custom-fields-component .fi-icon-btn.fi-size-sm:has(.fi-icon.fi-size-sm){margin:calc(var(--spacing)*-2)}.custom-fields-component .fi-icon-btn.fi-size-sm:has(.fi-icon.fi-size-md){margin:calc(var(--spacing)*-1.5)}.custom-fields-component .fi-icon-btn.fi-size-sm:has(.fi-icon.fi-size-lg){margin:calc(var(--spacing)*-1)}.custom-fields-component .fi-icon-btn.fi-size-md:has(.fi-icon.fi-size-sm){margin:calc(var(--spacing)*-2.5)}.custom-fields-component .fi-icon-btn.fi-size-md:has(.fi-icon.fi-size-lg){margin:calc(var(--spacing)*-1.5)}.custom-fields-component .fi-icon-btn.fi-size-lg{width:calc(var(--spacing)*10);height:calc(var(--spacing)*10)}.custom-fields-component .fi-icon-btn.fi-size-lg:has(.fi-icon.fi-size-sm){margin:calc(var(--spacing)*-3)}.custom-fields-component .fi-icon-btn.fi-size-lg:has(.fi-icon.fi-size-md){margin:calc(var(--spacing)*-2.5)}.custom-fields-component .fi-icon-btn.fi-size-lg:has(.fi-icon.fi-size-lg){margin:calc(var(--spacing)*-2)}.custom-fields-component .fi-icon-btn.fi-size-xl{width:calc(var(--spacing)*11);height:calc(var(--spacing)*11)}.custom-fields-component .fi-icon-btn.fi-size-xl:has(.fi-icon.fi-size-sm){margin:calc(var(--spacing)*-3.5)}.custom-fields-component .fi-icon-btn.fi-size-xl:has(.fi-icon.fi-size-md){margin:calc(var(--spacing)*-3)}.custom-fields-component .fi-icon-btn.fi-size-xl:has(.fi-icon.fi-size-lg){margin:calc(var(--spacing)*-2.5)}.custom-fields-component .fi-icon-btn.fi-color{color:var(--text)}.custom-fields-component .fi-icon-btn.fi-color:where(.dark,.dark *){color:var(--dark-text)}@media (hover:hover){.custom-fields-component :is(.fi-icon-btn.fi-color.fi-force-enabled,.fi-icon-btn.fi-color:not(.fi-disabled):not([disabled])):hover{color:var(--hover-text)}}.custom-fields-component :is(.fi-icon-btn.fi-color.fi-force-enabled,.fi-icon-btn.fi-color:not(.fi-disabled):not([disabled])):focus-visible{--tw-ring-color:var(--color-600)}@media (hover:hover){.custom-fields-component :is(.fi-icon-btn.fi-color.fi-force-enabled,.fi-icon-btn.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):hover{color:var(--dark-hover-text)}}.custom-fields-component :is(.fi-icon-btn.fi-color.fi-force-enabled,.fi-icon-btn.fi-color:not(.fi-disabled):not([disabled])):where(.dark,.dark *):focus-visible{--tw-ring-color:var(--color-500)}.custom-fields-component .fi-icon-btn>.fi-icon-btn-badge-ctn{inset-inline-start:100%;top:var(--spacing);z-index:1;--tw-translate-x:-50%;width:max-content;--tw-translate-y:-50%;translate:var(--tw-translate-x) var(--tw-translate-y);border-radius:var(--radius-md);background-color:var(--color-white);display:flex;position:absolute}.custom-fields-component .fi-icon-btn>.fi-icon-btn-badge-ctn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:50%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-icon-btn>.fi-icon-btn-badge-ctn:where(.dark,.dark *){background-color:var(--gray-900)}@media (min-width:40rem){.custom-fields-component .fi-icon-btn:has(+.fi-btn.fi-labeled-from-sm){display:none}}@media (min-width:48rem){.custom-fields-component .fi-icon-btn:has(+.fi-btn.fi-labeled-from-md){display:none}}@media (min-width:64rem){.custom-fields-component .fi-icon-btn:has(+.fi-btn.fi-labeled-from-lg){display:none}}@media (min-width:80rem){.custom-fields-component .fi-icon-btn:has(+.fi-btn.fi-labeled-from-xl){display:none}}@media (min-width:96rem){.custom-fields-component .fi-icon-btn:has(+.fi-btn.fi-labeled-from-2xl){display:none}}.custom-fields-component input[type=checkbox].fi-checkbox-input{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);appearance:none;--tw-border-style:none;background-color:var(--color-white);vertical-align:middle;color:var(--primary-600);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);border-style:none;border-radius:.25rem}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:checked{background-color:var(--primary-600);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component input[type=checkbox].fi-checkbox-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600);--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.custom-fields-component input[type=checkbox].fi-checkbox-input:checked:focus{--tw-ring-color:var(--primary-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:checked:focus{--tw-ring-color:color-mix(in oklab, var(--primary-500) 50%, transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:disabled{pointer-events:none;background-color:var(--gray-50);color:var(--gray-50)}.custom-fields-component input[type=checkbox].fi-checkbox-input:disabled:checked{background-color:var(--gray-400);color:var(--gray-400)}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *){color:var(--primary-500);--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):checked{background-color:var(--primary-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):focus{--tw-ring-color:var(--primary-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):checked:focus{--tw-ring-color:var(--primary-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):checked:focus{--tw-ring-color:color-mix(in oklab, var(--primary-400) 50%, transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):disabled{--tw-ring-color:#ffffff1a;background-color:#0000}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):disabled{--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:where(.dark,.dark *):disabled:checked{background-color:var(--gray-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22%23fff%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M12.207%204.793a1%201%200%200%201%200%201.414l-5%205a1%201%200%200%201-1.414%200l-2-2a1%201%200%200%201%201.414-1.414L6.5%209.086l4.293-4.293a1%201%200%200%201%201.414%200%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate{background-color:var(--primary-600);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:where(.dark,.dark *){background-color:var(--primary-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22%23fff%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M4.5%206.75a1.25%201.25%200%200%200%200%202.5h7a1.25%201.25%200%200%200%200-2.5z%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:focus{--tw-ring-color:color-mix(in oklab, var(--primary-500) 50%, transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:focus:where(.dark,.dark *){--tw-ring-color:var(--primary-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:focus:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--primary-400) 50%, transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:disabled{background-color:var(--gray-400)}.custom-fields-component input[type=checkbox].fi-checkbox-input:indeterminate:disabled:where(.dark,.dark *){background-color:var(--gray-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid{color:var(--danger-600);--tw-ring-color:var(--danger-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:checked{background-color:var(--danger-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:focus{--tw-ring-color:var(--danger-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:checked:focus{--tw-ring-color:var(--danger-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:checked:focus{--tw-ring-color:color-mix(in oklab, var(--danger-500) 50%, transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:where(.dark,.dark *){color:var(--danger-500);--tw-ring-color:var(--danger-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:where(.dark,.dark *):checked{background-color:var(--danger-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:where(.dark,.dark *):focus{--tw-ring-color:var(--danger-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:where(.dark,.dark *):checked:focus{--tw-ring-color:var(--danger-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:where(.dark,.dark *):checked:focus{--tw-ring-color:color-mix(in oklab, var(--danger-400) 50%, transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate{background-color:var(--danger-600)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate:where(.dark,.dark *){background-color:var(--danger-500)}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate:focus{--tw-ring-color:var(--danger-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate:focus{--tw-ring-color:color-mix(in oklab, var(--danger-500) 50%, transparent)}}.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate:focus:where(.dark,.dark *){--tw-ring-color:var(--danger-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=checkbox].fi-checkbox-input.fi-invalid:indeterminate:focus:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--danger-400) 50%, transparent)}}.custom-fields-component input.fi-input{appearance:none;--tw-border-style:none;width:100%;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);text-align:start;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);color:var(--gray-950);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;background-color:#0000;border-style:none;transition-duration:75ms;display:block}.custom-fields-component input.fi-input::placeholder{color:var(--gray-400)}.custom-fields-component input.fi-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}.custom-fields-component input.fi-input:disabled{color:var(--gray-500);-webkit-text-fill-color:var(--color-gray-500)}.custom-fields-component input.fi-input:disabled::placeholder{-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component input.fi-input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component input.fi-input:where(.dark,.dark *)::placeholder{color:var(--gray-500)}.custom-fields-component input.fi-input:where(.dark,.dark *):disabled{color:var(--gray-400);-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component input.fi-input:where(.dark,.dark *):disabled::placeholder{-webkit-text-fill-color:var(--color-gray-500)}@supports (-webkit-touch-callout:none){.custom-fields-component input.fi-input{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.custom-fields-component input.fi-input.fi-input-has-inline-prefix{padding-inline-start:0}.custom-fields-component input.fi-input.fi-input-has-inline-suffix{padding-inline-end:0}.custom-fields-component input.fi-input.fi-align-center{text-align:center}.custom-fields-component input.fi-input.fi-align-end{text-align:end}.custom-fields-component input.fi-input.fi-align-left{text-align:left}.custom-fields-component input.fi-input.fi-align-right{text-align:end}.custom-fields-component input.fi-input.fi-align-between,.custom-fields-component input.fi-input.fi-align-justify{text-align:justify}.custom-fields-component input[type=date].fi-input,.custom-fields-component input[type=datetime-local].fi-input,.custom-fields-component input[type=time].fi-input{background-color:#ffffff03}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=date].fi-input,.custom-fields-component input[type=datetime-local].fi-input,.custom-fields-component input[type=time].fi-input{background-color:color-mix(in oklab,var(--color-white) 1%,transparent)}}.custom-fields-component input[type=range].fi-input{appearance:auto;width:calc(100% - 1.5rem);margin-inline:auto}.custom-fields-component .fi-one-time-code-input-ctn{align-items:center;gap:calc(var(--spacing)*2);width:fit-content;display:flex}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit{height:calc(var(--spacing)*11);width:calc(var(--spacing)*10);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit{border-color:color-mix(in oklab,var(--gray-950) 10%,transparent)}}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit{background-color:var(--color-white);text-align:center;font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit:focus{border-color:var(--primary-600);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600);--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit:disabled{pointer-events:none;color:var(--gray-500);opacity:.7}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit:where(.dark,.dark *){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-one-time-code-input-ctn>.fi-one-time-code-input-digit:where(.dark,.dark *):focus{border-color:var(--primary-500);--tw-ring-color:var(--primary-500)}.custom-fields-component input[type=radio].fi-radio-input{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);appearance:none;--tw-border-style:none;background-color:var(--color-white);color:var(--primary-600);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);border-style:none;border-radius:3.40282e+38px}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component input[type=radio].fi-radio-input:checked{background-color:var(--primary-600);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component input[type=radio].fi-radio-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600);--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-outline-style:none;outline-style:none}.custom-fields-component input[type=radio].fi-radio-input:checked:focus{--tw-ring-color:var(--primary-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input:checked:focus{--tw-ring-color:color-mix(in oklab, var(--primary-500) 50%, transparent)}}.custom-fields-component input[type=radio].fi-radio-input:disabled{background-color:var(--gray-50);color:var(--gray-50)}.custom-fields-component input[type=radio].fi-radio-input:disabled:checked{background-color:var(--gray-400);color:var(--gray-400)}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *){color:var(--primary-500);--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):checked{background-color:var(--primary-500)}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):focus{--tw-ring-color:var(--primary-500)}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):checked:focus{--tw-ring-color:var(--primary-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):checked:focus{--tw-ring-color:color-mix(in oklab, var(--primary-400) 50%, transparent)}}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):disabled{--tw-ring-color:#ffffff1a;background-color:#0000}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):disabled{--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component input[type=radio].fi-radio-input:where(.dark,.dark *):disabled:checked{background-color:var(--gray-600)}.custom-fields-component input[type=radio].fi-radio-input:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22%23fff%22%20viewBox%3D%220%200%2016%2016%22%3E%3Ccircle%20cx%3D%228%22%20cy%3D%228%22%20r%3D%223%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid{color:var(--danger-600);--tw-ring-color:var(--danger-600)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:checked{background-color:var(--danger-600)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:focus{--tw-ring-color:var(--danger-600)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:checked:focus{--tw-ring-color:var(--danger-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:checked:focus{--tw-ring-color:color-mix(in oklab, var(--danger-500) 50%, transparent)}}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:where(.dark,.dark *){color:var(--danger-500);--tw-ring-color:var(--danger-500)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:where(.dark,.dark *):checked{background-color:var(--danger-500)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:where(.dark,.dark *):focus{--tw-ring-color:var(--danger-500)}.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:where(.dark,.dark *):checked:focus{--tw-ring-color:var(--danger-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component input[type=radio].fi-radio-input.fi-invalid:where(.dark,.dark *):checked:focus{--tw-ring-color:color-mix(in oklab, var(--danger-400) 50%, transparent)}}.custom-fields-component select.fi-select-input{appearance:none;--tw-border-style:none;width:100%;padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);color:var(--gray-950);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;background-color:#0000;border-style:none;padding-inline-start:calc(var(--spacing)*3);padding-inline-end:calc(var(--spacing)*8);transition-duration:75ms;display:block}.custom-fields-component select.fi-select-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}.custom-fields-component select.fi-select-input:disabled{color:var(--gray-500);-webkit-text-fill-color:var(--color-gray-500)}.custom-fields-component select.fi-select-input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component select.fi-select-input:where(.dark,.dark *):disabled{color:var(--gray-400);-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component select.fi-select-input optgroup{background-color:var(--color-white)}.custom-fields-component select.fi-select-input optgroup:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component select.fi-select-input option{background-color:var(--color-white)}.custom-fields-component select.fi-select-input option:where(.dark,.dark *){background-color:var(--gray-900)}@supports (-webkit-touch-callout:none){.custom-fields-component select.fi-select-input{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.custom-fields-component select.fi-select-input{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20stroke%3D%22%236b7280%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%20d%3D%22m6%208%204%204%204-4%22%2F%3E%3C%2Fsvg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em}.custom-fields-component select.fi-select-input:where(:dir(rtl),[dir=rtl],[dir=rtl] *){background-position:.5rem}.custom-fields-component select.fi-select-input.fi-select-input-has-inline-prefix{padding-inline-start:0}.custom-fields-component .fi-select-input .fi-select-input-ctn{position:relative}.custom-fields-component .fi-select-input div[x-ref=select]{min-height:calc(var(--spacing)*9)}.custom-fields-component .fi-select-input .fi-select-input-btn{min-height:calc(var(--spacing)*9);border-radius:var(--radius-lg);width:100%;padding-block:calc(var(--spacing)*1.5);text-align:start;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);color:var(--gray-950);padding-inline-start:calc(var(--spacing)*3);padding-inline-end:calc(var(--spacing)*8);display:flex}.custom-fields-component .fi-select-input .fi-select-input-btn:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-select-input .fi-select-input-btn:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-select-input .fi-select-input-btn{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20stroke%3D%22%236b7280%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%221.5%22%20d%3D%22m6%208%204%204%204-4%22%2F%3E%3C%2Fsvg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em}.custom-fields-component .fi-select-input .fi-select-input-btn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){background-position:.5rem}.custom-fields-component .fi-select-input .fi-select-input-value-ctn{text-wrap:wrap;word-break:break-word;align-items:center;width:100%;display:flex}.custom-fields-component .fi-select-input .fi-select-input-value-badges-ctn{gap:calc(var(--spacing)*1.5);flex-wrap:wrap;display:flex}.custom-fields-component .fi-select-input .fi-select-input-value-label{flex:1}.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);--tw-translate-y:-50%;translate:var(--tw-translate-x) var(--tw-translate-y);color:var(--gray-500);inset-inline-end:calc(var(--spacing)*8);position:absolute;top:50%}@media (hover:hover){.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn:hover{color:var(--gray-600)}}.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn:focus-visible{color:var(--gray-600);--tw-outline-style:none;outline-style:none}@media (hover:hover){.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn:where(.dark,.dark *):hover{color:var(--gray-300)}}.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn:where(.dark,.dark *):focus-visible{color:var(--gray-300)}.custom-fields-component .fi-select-input .fi-select-input-value-remove-btn{background-color:currentColor;-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M5.28%204.22a.75.75%200%200%200-1.06%201.06L6.94%208l-2.72%202.72a.75.75%200%201%200%201.06%201.06L8%209.06l2.72%202.72a.75.75%200%201%200%201.06-1.06L9.06%208l2.72-2.72a.75.75%200%200%200-1.06-1.06L8%206.94z%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M5.28%204.22a.75.75%200%200%200-1.06%201.06L6.94%208l-2.72%202.72a.75.75%200%201%200%201.06%201.06L8%209.06l2.72%202.72a.75.75%200%201%200%201.06-1.06L9.06%208l2.72-2.72a.75.75%200%200%200-1.06-1.06L8%206.94z%22%2F%3E%3C%2Fsvg%3E");-webkit-mask-position:50%;mask-position:50%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.custom-fields-component .fi-select-input .fi-select-input-ctn-clearable .fi-select-input-btn{padding-inline-end:calc(var(--spacing)*14)}.custom-fields-component .fi-select-input .fi-dropdown-panel{max-height:calc(var(--spacing)*60);max-width:100%!important}.custom-fields-component :where(.fi-select-input .fi-select-input-options-ctn>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-100)}.custom-fields-component :where(.fi-select-input .fi-select-input-options-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-select-input .fi-select-input-options-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component :where(.fi-select-input .fi-select-input-option-group>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-100)}.custom-fields-component :where(.fi-select-input .fi-select-input-option-group:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-select-input .fi-select-input-option-group:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-select-input .fi-select-input-option-group .fi-dropdown-header{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-500)}.custom-fields-component .fi-select-input .fi-select-input-option-group .fi-dropdown-header:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-select-input .fi-select-input-search-ctn{z-index:10;background-color:var(--color-white);position:sticky;top:0}.custom-fields-component .fi-select-input .fi-select-input-search-ctn:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-select-input .fi-select-input-option{text-wrap:wrap;word-break:break-word;min-width:1px}.custom-fields-component .fi-select-input .fi-select-input-placeholder{color:var(--gray-400)}.custom-fields-component .fi-select-input .fi-select-input-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-select-input .fi-disabled{cursor:not-allowed;opacity:.7}.custom-fields-component .fi-select-input .fi-disabled .fi-select-input-placeholder{color:var(--gray-400)}.custom-fields-component .fi-select-input .fi-disabled .fi-select-input-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-select-input .fi-select-input-message{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-select-input .fi-select-input-message:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-select-input .fi-select-input-max-items-message{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--danger-600)}.custom-fields-component .fi-select-input .fi-select-input-max-items-message:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-select-input .fi-select-input-ctn.fi-select-input-ctn-option-labels-not-wrapped .fi-select-input-value-ctn>span{text-overflow:ellipsis;white-space:nowrap;text-wrap:nowrap;overflow-wrap:normal;word-break:normal;overflow:hidden}.custom-fields-component .fi-select-input .fi-select-input-ctn.fi-select-input-ctn-option-labels-not-wrapped .fi-select-input-option>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.custom-fields-component .fi-input-wrp{border-radius:var(--radius-lg);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);display:flex}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-input-wrp{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-input-wrp:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-input-wrp:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)):focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)):where(.dark,.dark *):focus-within{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)).fi-invalid:focus-within{--tw-ring-color:var(--danger-600)}.custom-fields-component .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)).fi-invalid:where(.dark,.dark *):focus-within{--tw-ring-color:var(--danger-500)}.custom-fields-component .fi-input-wrp.fi-disabled{background-color:var(--gray-50)}.custom-fields-component .fi-input-wrp.fi-disabled:where(.dark,.dark *){background-color:#0000}.custom-fields-component .fi-input-wrp.fi-disabled:not(.fi-invalid):where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp.fi-disabled:not(.fi-invalid):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-input-wrp.fi-invalid{--tw-ring-color:var(--danger-600)}.custom-fields-component .fi-input-wrp.fi-invalid:where(.dark,.dark *){--tw-ring-color:var(--danger-500)}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix{align-items:center;column-gap:calc(var(--spacing)*3);padding-inline-start:calc(var(--spacing)*3);display:none}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix.fi-input-wrp-prefix-has-content{display:flex}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix.fi-inline{padding-inline-end:calc(var(--spacing)*2)}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix.fi-inline.fi-input-wrp-prefix-has-label{padding-inline-end:var(--spacing)}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix:not(.fi-inline){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px;border-color:var(--gray-200);padding-inline-start:calc(var(--spacing)*3);padding-inline-end:calc(var(--spacing)*3)}.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix:not(.fi-inline):where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp .fi-input-wrp-prefix:not(.fi-inline):where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-input-wrp .fi-input-wrp-content-ctn,.custom-fields-component .fi-input-wrp:not(:has(.fi-input-wrp-content-ctn))>*{flex:1;min-width:0}.custom-fields-component :is(.fi-input-wrp .fi-input-wrp-content-ctn,.fi-input-wrp:not(:has(.fi-input-wrp-content-ctn))>*).fi-input-wrp-content-ctn-ps{padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix{align-items:center;column-gap:calc(var(--spacing)*3);padding-inline-end:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix.fi-inline{padding-inline-start:calc(var(--spacing)*2)}.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix.fi-inline.fi-input-wrp-suffix-has-label{padding-inline-start:var(--spacing)}.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix:not(.fi-inline){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px;border-color:var(--gray-200);padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix:not(.fi-inline):where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-input-wrp .fi-input-wrp-suffix:not(.fi-inline):where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-input-wrp .fi-input-wrp-actions{align-items:center;gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-input-wrp .fi-input-wrp-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));white-space:nowrap;color:var(--gray-500)}.custom-fields-component .fi-input-wrp .fi-input-wrp-label:where(.dark,.dark *),.custom-fields-component :is(.fi-input-wrp .fi-input-wrp-prefix,.fi-input-wrp .fi-input-wrp-suffix)>.fi-icon{color:var(--gray-400)}.custom-fields-component :is(.fi-input-wrp .fi-input-wrp-prefix,.fi-input-wrp .fi-input-wrp-suffix)>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component :is(.fi-input-wrp .fi-input-wrp-prefix,.fi-input-wrp .fi-input-wrp-suffix)>.fi-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-link{justify-content:center;align-items:center;gap:calc(var(--spacing)*1.5);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-700);--tw-outline-style:none;outline-style:none;display:inline-flex;position:relative}.custom-fields-component .fi-link:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-link>.fi-link-label{align-self:baseline}@media (hover:hover){.custom-fields-component :is(.fi-link.fi-force-enabled,.fi-link:not(.fi-disabled):not([disabled])):hover{text-decoration-line:underline}}.custom-fields-component :is(.fi-link.fi-force-enabled,.fi-link:not(.fi-disabled):not([disabled])):focus-visible{border-radius:var(--radius-sm);outline-style:var(--tw-outline-style);outline-offset:2px;--tw-outline-style:solid;outline:2px solid;text-decoration-line:underline}.custom-fields-component .fi-link.fi-disabled:not(.fi-force-enabled),.custom-fields-component .fi-link[disabled]:not(.fi-force-enabled){cursor:default;opacity:.7}.custom-fields-component :is(.fi-link.fi-disabled:not(.fi-force-enabled),.fi-link[disabled]:not(.fi-force-enabled)):not([x-tooltip]){pointer-events:none}.custom-fields-component .fi-link.fi-size-xs{gap:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-link.fi-size-sm{gap:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-link.fi-size-lg,.custom-fields-component .fi-link.fi-size-md,.custom-fields-component .fi-link.fi-size-xl{gap:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-link.fi-font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.custom-fields-component .fi-link.fi-font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.custom-fields-component .fi-link.fi-font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.custom-fields-component .fi-link.fi-font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.custom-fields-component .fi-link.fi-font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-link.fi-font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.custom-fields-component .fi-link.fi-font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.custom-fields-component .fi-link.fi-font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.custom-fields-component .fi-link.fi-color{color:var(--text)}.custom-fields-component .fi-link.fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-link:not(.fi-color)>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-link:not(.fi-color)>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-link .fi-link-badge-ctn{z-index:1;--tw-translate-x:-25%;width:max-content;--tw-translate-y:-75%;translate:var(--tw-translate-x) var(--tw-translate-y);border-radius:var(--radius-md);background-color:var(--color-white);--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);inset-inline-start:100%;display:flex;position:absolute;top:0}@media (hover:hover){.custom-fields-component .fi-link .fi-link-badge-ctn:hover{text-decoration-line:none}}.custom-fields-component .fi-link .fi-link-badge-ctn:focus-visible{text-decoration-line:none}.custom-fields-component .fi-link .fi-link-badge-ctn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:25%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-link .fi-link-badge-ctn:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component p>.fi-link,.custom-fields-component span>.fi-link{vertical-align:middle;text-align:inherit;padding-bottom:2px}@media (prefers-reduced-motion:no-preference){.custom-fields-component .fi-loading-indicator{animation:var(--animate-spin)}.custom-fields-component .fi-loading-section{animation:var(--animate-pulse)}}.custom-fields-component .fi-modal{--tw-outline-style:none;outline-style:none}.custom-fields-component :is(.fi-modal.fi-modal-slide-over,.fi-modal.fi-width-screen)>.fi-modal-window-ctn>.fi-modal-window{height:100dvh}.custom-fields-component :is(.fi-modal.fi-modal-slide-over,.fi-modal.fi-width-screen)>.fi-modal-window-ctn>.fi-modal-window .fi-modal-content{flex:1}.custom-fields-component .fi-modal.fi-modal-slide-over.fi-modal-slide-over-from-start>.fi-modal-window-ctn>.fi-modal-window{margin-inline-end:auto}.custom-fields-component .fi-modal.fi-modal-slide-over.fi-modal-slide-over-from-start>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-start,.custom-fields-component .fi-modal.fi-modal-slide-over.fi-modal-slide-over-from-start>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-end{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component :is(.fi-modal.fi-modal-slide-over.fi-modal-slide-over-from-start>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-start,.fi-modal.fi-modal-slide-over.fi-modal-slide-over-from-start>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-end):where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-modal.fi-modal-slide-over.fi-modal-slide-over-from-end>.fi-modal-window-ctn>.fi-modal-window{margin-inline-start:auto}.custom-fields-component .fi-modal.fi-modal-slide-over.fi-modal-slide-over-from-end>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-start,.custom-fields-component .fi-modal.fi-modal-slide-over.fi-modal-slide-over-from-end>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-end{--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component :is(.fi-modal.fi-modal-slide-over.fi-modal-slide-over-from-end>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-start,.fi-modal.fi-modal-slide-over.fi-modal-slide-over-from-end>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-end):where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window{overflow-y:auto}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-end,.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-start{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window .fi-modal-header{column-gap:calc(var(--spacing)*3);padding-top:calc(var(--spacing)*5)}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg{margin-block:calc(var(--spacing)*-2);padding:calc(var(--spacing)*2);margin-inline-start:calc(var(--spacing)*-2)}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window .fi-modal-close-btn{inset-inline-end:calc(var(--spacing)*6);top:calc(var(--spacing)*5)}.custom-fields-component .fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen):not(.fi-modal-has-sticky-header):not(.fi-modal-has-sticky-footer)>.fi-modal-window-ctn{overflow-y:auto}.custom-fields-component :is(.fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen).fi-modal-has-sticky-header,.fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen).fi-modal-has-sticky-footer)>.fi-modal-window-ctn>.fi-modal-window{max-height:calc(100dvh - 2rem);overflow-y:auto}.custom-fields-component .fi-modal:not(.fi-modal-slide-over)>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-start,.custom-fields-component .fi-modal:not(.fi-modal-slide-over)>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-end{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y);opacity:0}.custom-fields-component .fi-modal:not(.fi-modal-slide-over)>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter-end,.custom-fields-component .fi-modal:not(.fi-modal-slide-over)>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave-start{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y);opacity:1}.custom-fields-component .fi-modal:not(.fi-modal-slide-over)>.fi-modal-window-ctn>.fi-modal-window .fi-modal-close-btn{inset-inline-end:calc(var(--spacing)*4);top:calc(var(--spacing)*4)}.custom-fields-component .fi-modal.fi-align-start:not(.fi-modal-has-sticky-header)>.fi-modal-window-ctn>.fi-modal-window-has-icon .fi-modal-content,.custom-fields-component .fi-modal.fi-align-start:not(.fi-modal-has-sticky-header)>.fi-modal-window-ctn>.fi-modal-window-has-icon .fi-modal-footer:not(.fi-align-center){padding-inline-start:5.25rem;padding-inline-end:calc(var(--spacing)*6)}.custom-fields-component .fi-modal:not(.fi-align-start)>.fi-modal-window-ctn>.fi-modal-window .fi-modal-content,.custom-fields-component .fi-modal:not(.fi-align-start)>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer{padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-modal>.fi-modal-close-overlay{z-index:40;background-color:var(--gray-950);position:fixed;inset:0}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-close-overlay{background-color:color-mix(in oklab,var(--gray-950) 50%,transparent)}}.custom-fields-component .fi-modal>.fi-modal-close-overlay{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;transition-duration:.3s}.custom-fields-component .fi-modal>.fi-modal-close-overlay:where(.dark,.dark *){background-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-close-overlay:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-950) 75%,transparent)}}.custom-fields-component .fi-modal.fi-modal-open:has(~.fi-modal.fi-modal-open)>.fi-modal-close-overlay{opacity:0}.custom-fields-component .fi-modal.fi-modal-open~.fi-modal.fi-modal-open>.fi-modal-close-overlay,.custom-fields-component .fi-modal.fi-modal-open~.fi-modal.fi-modal-open>.fi-modal-window-ctn{z-index:50}.custom-fields-component .fi-modal>.fi-modal-window-ctn{z-index:40;grid-template-rows:1fr auto 1fr;justify-items:center;min-height:100%;display:grid;position:fixed;inset:0}@media (min-width:40rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn{grid-template-rows:1fr auto 3fr}}.custom-fields-component .fi-modal>.fi-modal-window-ctn.fi-clickable{cursor:pointer}.custom-fields-component .fi-modal.fi-modal-click-through{pointer-events:none}.custom-fields-component .fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen)>.fi-modal-window-ctn{padding:calc(var(--spacing)*4)}.custom-fields-component .fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen)>.fi-modal-window-ctn>.fi-modal-window{border-radius:var(--radius-xl);margin-inline:auto}.custom-fields-component .fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen).fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.custom-fields-component .fi-modal:not(.fi-modal-slide-over):not(.fi-width-screen).fi-modal-has-sticky-footer>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer{border-bottom-right-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window{pointer-events:auto;cursor:default;background-color:var(--color-white);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);width:100%;--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);flex-direction:column;grid-row-start:2;display:flex;position:relative}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window{--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-header{padding-inline:calc(var(--spacing)*6);padding-top:calc(var(--spacing)*6);display:flex}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-header.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-description{margin-top:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-content{row-gap:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*6);flex-direction:column;display:flex}.custom-fields-component :is(.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-start,.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-left) .fi-modal-header{column-gap:calc(var(--spacing)*5)}.custom-fields-component :is(.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-start,.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-left) .fi-modal-icon-bg{padding:calc(var(--spacing)*2)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-center .fi-modal-header{text-align:center;flex-direction:column}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-center .fi-modal-icon-ctn{margin-bottom:calc(var(--spacing)*5);justify-content:center;align-items:center;display:flex}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-align-center .fi-modal-icon-bg{padding:calc(var(--spacing)*3)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-hidden{display:none}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-3xs{max-width:var(--container-3xs)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-2xs{max-width:var(--container-2xs)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-xs{max-width:var(--container-xs)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-sm{max-width:var(--container-sm)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-md{max-width:var(--container-md)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-lg{max-width:var(--container-lg)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-xl{max-width:var(--container-xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-2xl{max-width:var(--container-2xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-3xl{max-width:var(--container-3xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-4xl{max-width:var(--container-4xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-5xl{max-width:var(--container-5xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-6xl{max-width:var(--container-6xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-7xl{max-width:var(--container-7xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-none{max-width:none}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-full{max-width:100%}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-min{max-width:min-content}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-max{max-width:max-content}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-fit{max-width:fit-content}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-prose{max-width:65ch}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-container{width:100%}@media (min-width:40rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-container{max-width:40rem}}@media (min-width:48rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-container{max-width:48rem}}@media (min-width:64rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-container{max-width:64rem}}@media (min-width:80rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-container{max-width:80rem}}@media (min-width:96rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-container{max-width:96rem}}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen-sm{max-width:var(--breakpoint-sm)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen-md{max-width:var(--breakpoint-md)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen-lg{max-width:var(--breakpoint-lg)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen-xl{max-width:var(--breakpoint-xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen-2xl{max-width:var(--breakpoint-2xl)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-width-screen{position:fixed;inset:0}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-transition-enter,.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-transition-leave{--tw-duration:.3s;transition-duration:.3s}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-close-btn.fi-align-center:not(.fi-modal-window-has-icon) .fi-modal-heading{margin-inline-start:calc(var(--spacing)*6)}.custom-fields-component :is(.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-close-btn:not(.fi-modal-window-has-icon),.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-close-btn.fi-align-start,.fi-modal>.fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-close-btn.fi-align-left) .fi-modal-heading{margin-inline-end:calc(var(--spacing)*6)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-close-btn{position:absolute}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer{width:100%}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer .fi-modal-footer-actions{gap:calc(var(--spacing)*3)}.custom-fields-component :is(.fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-start,.fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-left) .fi-modal-footer-actions{flex-wrap:wrap;align-items:center;display:flex}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-center{padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-center .fi-modal-footer-actions{flex-direction:column-reverse;display:flex}.custom-fields-component :is(.fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-end,.fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-right) .fi-modal-footer-actions{flex-flow:row-reverse wrap;align-items:center;display:flex}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg{background-color:var(--gray-100);border-radius:3.40282e+38px}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg:where(.dark,.dark *){background-color:var(--gray-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-500) 20%,transparent)}}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg>.fi-icon{color:var(--gray-500)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg>.fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg.fi-color{background-color:var(--color-100)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg.fi-color:where(.dark,.dark *){background-color:var(--color-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg.fi-color:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-500) 20%,transparent)}}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg.fi-color>.fi-icon{color:var(--color-600)}.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-icon-bg.fi-color>.fi-icon:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header{z-index:10;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);background-color:var(--color-white);padding-bottom:calc(var(--spacing)*6);position:sticky;top:0}.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window .fi-modal-content,.custom-fields-component .fi-modal.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer{padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-modal.fi-modal-slide-over.fi-modal-has-sticky-header>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-header{padding-bottom:calc(var(--spacing)*5)}.custom-fields-component .fi-modal.fi-modal-has-sticky-footer>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-200);background-color:var(--color-white);padding-block:calc(var(--spacing)*5);position:sticky;bottom:0}.custom-fields-component .fi-modal.fi-modal-has-sticky-footer>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-modal.fi-modal-has-sticky-footer>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-modal.fi-modal-has-sticky-footer>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-modal:not(.fi-modal-has-sticky-footer)>.fi-modal-window-ctn>.fi-modal-window:not(.fi-modal-window-has-content) .fi-modal-footer{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-modal:not(.fi-modal-has-sticky-footer)>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer,.custom-fields-component .fi-modal:not(.fi-modal-has-sticky-footer)>.fi-modal-window-ctn>.fi-modal-window:not(.fi-modal-window-has-content):not(.fi-modal-window-has-footer) .fi-modal-header{padding-bottom:calc(var(--spacing)*6)}.custom-fields-component .fi-modal:not(.fi-modal-has-sticky-header)>.fi-modal-window-ctn>.fi-modal-window:not(.fi-modal-window-has-icon) .fi-modal-content,.custom-fields-component .fi-modal:not(.fi-modal-has-sticky-header)>.fi-modal-window-ctn>.fi-modal-window:not(.fi-modal-window-has-icon) .fi-modal-footer{padding-inline:calc(var(--spacing)*6)}.custom-fields-component .fi-modal.fi-modal-slide-over>.fi-modal-window-ctn>.fi-modal-window>.fi-modal-footer{margin-top:auto}@supports (container-type:inline-size){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-center{container-type:inline-size}@container (min-width:24rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-center .fi-modal-footer-actions{grid-template-columns:repeat(auto-fit,minmax(0,1fr));display:grid}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-modal>.fi-modal-window-ctn>.fi-modal-window .fi-modal-footer.fi-align-center .fi-modal-footer-actions{grid-template-columns:repeat(auto-fit,minmax(0,1fr));display:grid}}}.custom-fields-component :scope .fi-modal-trigger{display:flex}.custom-fields-component .fi-pagination{align-items:center;column-gap:calc(var(--spacing)*3);grid-template-columns:1fr auto 1fr;display:grid}.custom-fields-component .fi-pagination:empty{display:none}.custom-fields-component .fi-pagination .fi-pagination-previous-btn{justify-self:flex-start}.custom-fields-component .fi-pagination .fi-pagination-overview{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-700);display:none}.custom-fields-component .fi-pagination .fi-pagination-overview:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select-ctn{grid-column-start:2;justify-self:center}.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select:not(.fi-compact){display:none}.custom-fields-component .fi-pagination .fi-pagination-next-btn{grid-column-start:3;justify-self:flex-end}.custom-fields-component .fi-pagination .fi-pagination-items{border-radius:var(--radius-lg);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);justify-self:flex-end;display:none}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-items{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-pagination .fi-pagination-items:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-items:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-pagination .fi-pagination-items:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-items:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-pagination .fi-pagination-item{border-inline-style:var(--tw-border-style);border-inline-width:.5px;border-color:var(--gray-200)}.custom-fields-component .fi-pagination .fi-pagination-item:first-child{border-inline-start-style:var(--tw-border-style);border-inline-start-width:0}.custom-fields-component .fi-pagination .fi-pagination-item:last-child{border-inline-end-style:var(--tw-border-style);border-inline-end-width:0}.custom-fields-component .fi-pagination .fi-pagination-item:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-item:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-pagination .fi-pagination-item.fi-active .fi-pagination-item-btn{background-color:var(--gray-50)}.custom-fields-component .fi-pagination .fi-pagination-item.fi-active .fi-pagination-item-btn:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-item.fi-active .fi-pagination-item-btn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-pagination .fi-pagination-item.fi-active .fi-pagination-item-label{color:var(--primary-700)}.custom-fields-component .fi-pagination .fi-pagination-item.fi-active .fi-pagination-item-label:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-pagination .fi-pagination-item:first-of-type .fi-pagination-item-btn{border-start-start-radius:var(--radius-lg);border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-pagination .fi-pagination-item:last-of-type .fi-pagination-item-btn{border-start-end-radius:var(--radius-lg);border-end-end-radius:var(--radius-lg)}.custom-fields-component .fi-pagination .fi-pagination-item.fi-disabled .fi-pagination-item-label{color:var(--gray-500)}.custom-fields-component .fi-pagination .fi-pagination-item.fi-disabled .fi-pagination-item-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-pagination .fi-pagination-item-btn{padding:calc(var(--spacing)*2);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;--tw-outline-style:none;outline-style:none;transition-duration:75ms;display:flex;position:relative;overflow:hidden}@media (hover:hover){.custom-fields-component .fi-pagination .fi-pagination-item-btn:enabled:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-pagination .fi-pagination-item-btn:enabled:focus-visible{z-index:10;--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}@media (hover:hover){.custom-fields-component .fi-pagination .fi-pagination-item-btn:enabled:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-pagination .fi-pagination-item-btn:enabled:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-pagination .fi-pagination-item-btn:enabled:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-pagination .fi-pagination-item-btn:hover .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-pagination .fi-pagination-item-btn:hover .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-pagination .fi-pagination-item-btn .fi-icon{color:var(--gray-400);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-pagination .fi-pagination-item-btn .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-pagination .fi-pagination-item-btn .fi-pagination-item-label{padding-inline:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-700)}.custom-fields-component .fi-pagination .fi-pagination-item-btn .fi-pagination-item-label:where(.dark,.dark *){color:var(--gray-200)}@supports (container-type:inline-size){.custom-fields-component .fi-pagination{container-type:inline-size}@container (min-width:28rem){.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select:not(.fi-compact){display:inline}.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select.fi-compact{display:none}}@container (min-width:56rem){.custom-fields-component .fi-pagination:not(.fi-simple) .fi-pagination-next-btn,.custom-fields-component .fi-pagination:not(.fi-simple) .fi-pagination-previous-btn{display:none}.custom-fields-component .fi-pagination .fi-pagination-overview{display:inline}.custom-fields-component .fi-pagination .fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select:not(.fi-compact){display:inline}.custom-fields-component .fi-pagination .fi-pagination-records-per-page-select.fi-compact{display:none}}@media (min-width:48rem){.custom-fields-component .fi-pagination:not(.fi-simple) .fi-pagination-next-btn,.custom-fields-component .fi-pagination:not(.fi-simple) .fi-pagination-previous-btn{display:none}.custom-fields-component .fi-pagination .fi-pagination-overview{display:inline}.custom-fields-component .fi-pagination .fi-pagination-items{display:flex}}}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-divided>.fi-section-content-ctn>.fi-section-content>*,.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-divided)>.fi-section-content-ctn>.fi-section-content{padding:calc(var(--spacing)*6)}.custom-fields-component .fi-section:not(.fi-section-not-contained)>.fi-section-content-ctn>.fi-section-footer{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-200);padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-section:not(.fi-section-not-contained)>.fi-section-content-ctn>.fi-section-footer:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained)>.fi-section-content-ctn>.fi-section-footer:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside){border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside){--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside):where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-compact{border-radius:var(--radius-lg)}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-secondary{background-color:var(--gray-50)}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-secondary:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-secondary:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside)>.fi-section-header{padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-section-has-header:not(.fi-collapsed)>.fi-section-content-ctn{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-200)}.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-section-has-header:not(.fi-collapsed)>.fi-section-content-ctn:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained):not(.fi-aside).fi-section-has-header:not(.fi-collapsed)>.fi-section-content-ctn:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside>.fi-section-content-ctn{border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside>.fi-section-content-ctn{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}@media (min-width:48rem){.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside>.fi-section-content-ctn{grid-column:span 2/span 2}}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside>.fi-section-content-ctn:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside>.fi-section-content-ctn:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside.fi-compact>.fi-section-content-ctn{border-radius:var(--radius-lg)}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside.fi-secondary>.fi-section-content-ctn{background-color:var(--gray-50)}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside.fi-secondary>.fi-section-content-ctn:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-aside.fi-secondary>.fi-section-content-ctn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-compact:not(.fi-aside)>.fi-section-header{padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2.5)}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-compact.fi-divided>.fi-section-content-ctn>.fi-section-content>*,.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-compact:not(.fi-divided)>.fi-section-content-ctn>.fi-section-content{padding:calc(var(--spacing)*4)}.custom-fields-component .fi-section:not(.fi-section-not-contained).fi-compact>.fi-section-footer{padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2.5)}@media (min-width:48rem){.custom-fields-component .fi-section.fi-section-not-contained.fi-aside>.fi-section-content-ctn{grid-column:span 2/span 2}}.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside),.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside)>.fi-section-content-ctn{row-gap:calc(var(--spacing)*4);display:grid}.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside).fi-divided>.fi-section-content-ctn>.fi-section-content>*{padding-block:calc(var(--spacing)*6)}.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside).fi-compact,.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside).fi-compact>.fi-section-content-ctn{row-gap:calc(var(--spacing)*2.5)}.custom-fields-component .fi-section.fi-section-not-contained:not(.fi-aside).fi-compact.fi-divided>.fi-section-content-ctn>.fi-section-content>*{padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-section.fi-divided>.fi-section-content-ctn>.fi-section-content{gap:0}.custom-fields-component :where(.fi-section.fi-divided>.fi-section-content-ctn>.fi-section-content>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-section.fi-divided>.fi-section-content-ctn>.fi-section-content:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-section.fi-divided>.fi-section-content-ctn>.fi-section-content:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-section.fi-aside{align-items:flex-start;column-gap:calc(var(--spacing)*6);row-gap:calc(var(--spacing)*4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}@media (min-width:48rem){.custom-fields-component .fi-section.fi-aside{grid-template-columns:repeat(3,minmax(0,1fr))}}.custom-fields-component .fi-section.fi-collapsible>.fi-section-header{cursor:pointer}.custom-fields-component .fi-section.fi-collapsed>.fi-section-header>.fi-section-collapse-btn{rotate:180deg}.custom-fields-component .fi-section.fi-collapsed>.fi-section-content-ctn{display:none}@media (min-width:48rem){.custom-fields-component .fi-section.fi-section-has-content-before>.fi-section-content-ctn{order:-9999}}.custom-fields-component .fi-section>.fi-section-header{align-items:flex-start;gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-section>.fi-section-header>.fi-icon{color:var(--gray-400);flex-shrink:0}.custom-fields-component .fi-section>.fi-section-header>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-section>.fi-section-header>.fi-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-section>.fi-section-header>.fi-icon.fi-color:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-section>.fi-section-header>.fi-icon.fi-size-sm{margin-top:var(--spacing)}.custom-fields-component .fi-section>.fi-section-header>.fi-icon.fi-size-md{margin-top:calc(var(--spacing)*.5)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn{align-self:center}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn .fi-link:not(.fi-section-header-after-ctn .fi-dropdown-panel *),.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn .fi-sc-text:not(.fi-section-header-after-ctn .fi-dropdown-panel *){--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn:has(.fi-btn.fi-size-xs:not(.fi-dropdown-panel *)){margin-block:calc(var(--spacing)*-.5)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn:has(.fi-btn.fi-size-sm:not(.fi-dropdown-panel *)){margin-block:calc(var(--spacing)*-1)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn:has(.fi-btn.fi-size-md:not(.fi-dropdown-panel *)){margin-block:calc(var(--spacing)*-1.5)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn:has(.fi-btn.fi-size-lg:not(.fi-dropdown-panel *)){margin-block:calc(var(--spacing)*-2)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-header-after-ctn:has(.fi-btn.fi-size-xl:not(.fi-dropdown-panel *)){margin-block:calc(var(--spacing)*-2.5)}.custom-fields-component .fi-section>.fi-section-header>.fi-section-collapse-btn{margin-block:calc(var(--spacing)*-1.5);flex-shrink:0}.custom-fields-component .fi-section .fi-section-header-text-ctn{row-gap:var(--spacing);flex:1;display:grid}.custom-fields-component .fi-section .fi-section-header-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}.custom-fields-component .fi-section .fi-section-header-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-section .fi-section-header-description{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));overflow-wrap:break-word;color:var(--gray-500);overflow:hidden}.custom-fields-component .fi-section .fi-section-header-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-tabs{column-gap:var(--spacing);max-width:100%;display:flex;overflow-x:auto}.custom-fields-component .fi-tabs.fi-contained{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2.5)}.custom-fields-component .fi-tabs.fi-contained:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs.fi-contained:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-tabs:not(.fi-contained){border-radius:var(--radius-xl);background-color:var(--color-white);padding:calc(var(--spacing)*2);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);margin-inline:auto}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs:not(.fi-contained){--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-tabs:not(.fi-contained):where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs:not(.fi-contained):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-tabs.fi-vertical{column-gap:0;row-gap:var(--spacing);flex-direction:column;overflow:hidden auto}.custom-fields-component .fi-tabs.fi-vertical.fi-contained{border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px;border-bottom-style:var(--tw-border-style);border-bottom-width:0}.custom-fields-component .fi-tabs.fi-vertical:not(.fi-contained){margin-inline:0}.custom-fields-component .fi-tabs.fi-vertical .fi-tabs-item{justify-content:flex-start}.custom-fields-component .fi-tabs-item{justify-content:center;align-items:center;column-gap:calc(var(--spacing)*2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;--tw-outline-style:none;outline-style:none;transition-duration:75ms;display:flex}@media (hover:hover){.custom-fields-component .fi-tabs-item:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-tabs-item:focus-visible{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-tabs-item:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs-item:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-tabs-item:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs-item:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-tabs-item.fi-active{background-color:var(--gray-50)}.custom-fields-component .fi-tabs-item.fi-active:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tabs-item.fi-active:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-tabs-item.fi-active .fi-tabs-item-label,.custom-fields-component .fi-tabs-item.fi-active>.fi-icon{color:var(--primary-700)}.custom-fields-component :is(.fi-tabs-item.fi-active .fi-tabs-item-label,.fi-tabs-item.fi-active>.fi-icon):where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-tabs-item :not(.fi-active):hover .fi-tabs-item-label,.custom-fields-component .fi-tabs-item :not(.fi-active):hover .fi-tabs-item-label:is(:where(.group):focus-visible *){color:var(--gray-700)}.custom-fields-component .fi-tabs-item :not(.fi-active):hover .fi-tabs-item-label:where(.dark,.dark *),.custom-fields-component .fi-tabs-item :not(.fi-active):hover .fi-tabs-item-label:where(.dark,.dark *):is(:where(.group):focus-visible *){color:var(--gray-200)}.custom-fields-component .fi-tabs-item :not(.fi-active):focus-visible .fi-tabs-item-label{color:var(--gray-700)}.custom-fields-component .fi-tabs-item :not(.fi-active):focus-visible .fi-tabs-item-label:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-tabs-item .fi-tabs-item-label{color:var(--gray-500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-tabs-item .fi-tabs-item-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-tabs-item>.fi-icon{color:var(--gray-400);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;flex-shrink:0;transition-duration:75ms}.custom-fields-component .fi-tabs-item>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-tabs-item .fi-badge{width:max-content}.custom-fields-component .fi-tabs-item .fi-tabs-item-badge-placeholder{color:var(--gray-400);align-items:center;display:flex}.custom-fields-component .fi-tabs-item .fi-tabs-item-badge-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-toggle{height:calc(var(--spacing)*6);width:calc(var(--spacing)*11);cursor:pointer;border-style:var(--tw-border-style);background-color:var(--gray-200);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:var(--ease-in-out);transition-duration:.2s;transition-timing-function:var(--ease-in-out);--tw-outline-style:none;border-width:2px;border-color:#0000;border-radius:3.40282e+38px;outline-style:none;flex-shrink:0;display:inline-flex;position:relative}.custom-fields-component .fi-toggle:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600);--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.custom-fields-component .fi-toggle:disabled{pointer-events:none;opacity:.7}.custom-fields-component .fi-toggle:where(.dark,.dark *){background-color:var(--gray-700)}.custom-fields-component .fi-toggle:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500);--tw-ring-offset-color:var(--gray-900)}.custom-fields-component .fi-toggle:disabled,.custom-fields-component .fi-toggle[disabled]{pointer-events:none;opacity:.7}.custom-fields-component .fi-toggle.fi-color{background-color:var(--bg)}.custom-fields-component .fi-toggle.fi-color:where(.dark,.dark *){background-color:var(--dark-bg)}.custom-fields-component .fi-toggle.fi-color .fi-icon{color:var(--text)}.custom-fields-component .fi-toggle.fi-hidden{display:none}.custom-fields-component .fi-toggle>:first-child{pointer-events:none;width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:var(--ease-in-out);transition-duration:.2s;transition-timing-function:var(--ease-in-out);border-radius:3.40282e+38px;display:inline-block;position:relative}.custom-fields-component .fi-toggle>:first-child>*{width:100%;height:100%;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.custom-fields-component .fi-toggle .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-toggle .fi-icon:where(.dark,.dark *){color:var(--gray-700)}.custom-fields-component .fi-toggle.fi-toggle-on>:first-child{--tw-translate-x:calc(var(--spacing)*5);translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-toggle.fi-toggle-on>:first-child:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:calc(var(--spacing)*-5);translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-toggle.fi-toggle-on>:first-child>:first-child{opacity:0;--tw-duration:.1s;--tw-ease:var(--ease-out);transition-duration:.1s;transition-timing-function:var(--ease-out)}.custom-fields-component .fi-toggle.fi-toggle-on>:first-child>:last-child{opacity:1;--tw-duration:.2s;--tw-ease:var(--ease-in);transition-duration:.2s;transition-timing-function:var(--ease-in)}.custom-fields-component .fi-toggle.fi-toggle-off>:first-child{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-toggle.fi-toggle-off>:first-child>:first-child{opacity:1;--tw-duration:.2s;--tw-ease:var(--ease-in);transition-duration:.2s;transition-timing-function:var(--ease-in)}.custom-fields-component .fi-toggle.fi-toggle-off>:first-child>:last-child{opacity:0;--tw-duration:.1s;--tw-ease:var(--ease-out);transition-duration:.1s;transition-timing-function:var(--ease-out)}.custom-fields-component .tippy-box{background-color:var(--tippy-background-color,#333)}.custom-fields-component .tippy-arrow{color:var(--tippy-background-color,#333)}.custom-fields-component .tippy-box[data-theme~=light]{--tippy-background-color:#fff;background-color:var(--tippy-background-color);box-shadow:var(--tippy-box-shadow,0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926)}.custom-fields-component .tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:var(--tippy-background-color)}.custom-fields-component .tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:var(--tippy-background-color)}.custom-fields-component .tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:var(--tippy-background-color)}.custom-fields-component .tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:var(--tippy-background-color)}.custom-fields-component .fi-sortable-ghost{opacity:.3}.custom-fields-component .fi-ac{gap:calc(var(--spacing)*3)}.custom-fields-component .fi-ac:not(.fi-width-full){flex-wrap:wrap;align-items:center;display:flex}.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-left,.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-start{justify-content:flex-start}.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-center{justify-content:center}.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-end,.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-right{flex-direction:row-reverse}.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-between,.custom-fields-component .fi-ac:not(.fi-width-full).fi-align-justify{justify-content:space-between}.custom-fields-component .fi-ac.fi-width-full{grid-template-columns:repeat(auto-fit,minmax(0,1fr));display:grid}.custom-fields-component .CodeMirror{color:#000;direction:ltr;height:300px;font-family:monospace}.custom-fields-component .CodeMirror-lines{padding:4px 0}.custom-fields-component .CodeMirror pre.CodeMirror-line,.custom-fields-component .CodeMirror pre.CodeMirror-line-like{padding:0 4px}.custom-fields-component .CodeMirror-gutter-filler,.custom-fields-component .CodeMirror-scrollbar-filler{background-color:#fff}.custom-fields-component .CodeMirror-gutters{white-space:nowrap;background-color:#f7f7f7;border-right:1px solid #ddd}.custom-fields-component .CodeMirror-linenumber{text-align:right;color:#999;white-space:nowrap;min-width:20px;padding:0 3px 0 5px}.custom-fields-component .CodeMirror-guttermarker{color:#000}.custom-fields-component .CodeMirror-guttermarker-subtle{color:#999}.custom-fields-component .CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.custom-fields-component .CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.custom-fields-component .cm-fat-cursor .CodeMirror-cursor{background:#7e7;width:auto;border:0!important}.custom-fields-component .cm-fat-cursor div.CodeMirror-cursors{z-index:1}.custom-fields-component .cm-fat-cursor .CodeMirror-line::selection{background:0 0}.custom-fields-component .cm-fat-cursor .CodeMirror-line>span::selection{background:0 0}.custom-fields-component .cm-fat-cursor .CodeMirror-line>span>span::selection{background:0 0}.custom-fields-component .cm-fat-cursor .CodeMirror-line::-moz-selection,.custom-fields-component .cm-fat-cursor .CodeMirror-line>span::-moz-selection{background:0 0}.custom-fields-component .cm-fat-cursor .CodeMirror-line>span>span::-moz-selection{background:0 0}.custom-fields-component .cm-fat-cursor{caret-color:#0000}@keyframes blink{50%{background-color:#0000}}.custom-fields-component .cm-tab{-webkit-text-decoration:inherit;text-decoration:inherit;display:inline-block}.custom-fields-component .CodeMirror-rulers{position:absolute;inset:-50px 0 0;overflow:hidden}.custom-fields-component .CodeMirror-ruler{border-left:1px solid #ccc;position:absolute;top:0;bottom:0}.custom-fields-component .cm-s-default .cm-header{color:#00f}.custom-fields-component .cm-s-default .cm-quote{color:#090}.custom-fields-component .cm-negative{color:#d44}.custom-fields-component .cm-positive{color:#292}.custom-fields-component .cm-header,.custom-fields-component .cm-strong{font-weight:700}.custom-fields-component .cm-em{font-style:italic}.custom-fields-component .cm-link{text-decoration:underline}.custom-fields-component .cm-strikethrough{text-decoration:line-through}.custom-fields-component .cm-s-default .cm-keyword{color:#708}.custom-fields-component .cm-s-default .cm-atom{color:#219}.custom-fields-component .cm-s-default .cm-number{color:#164}.custom-fields-component .cm-s-default .cm-def{color:#00f}.custom-fields-component .cm-s-default .cm-variable-2{color:#05a}.custom-fields-component .cm-s-default .cm-type,.custom-fields-component .cm-s-default .cm-variable-3{color:#085}.custom-fields-component .cm-s-default .cm-comment{color:#a50}.custom-fields-component .cm-s-default .cm-string{color:#a11}.custom-fields-component .cm-s-default .cm-string-2{color:#f50}.custom-fields-component .cm-s-default .cm-meta,.custom-fields-component .cm-s-default .cm-qualifier{color:#555}.custom-fields-component .cm-s-default .cm-builtin{color:#30a}.custom-fields-component .cm-s-default .cm-bracket{color:#997}.custom-fields-component .cm-s-default .cm-tag{color:#170}.custom-fields-component .cm-s-default .cm-attribute{color:#00c}.custom-fields-component .cm-s-default .cm-hr{color:#999}.custom-fields-component .cm-s-default .cm-link{color:#00c}.custom-fields-component .cm-invalidchar,.custom-fields-component .cm-s-default .cm-error{color:red}.custom-fields-component .CodeMirror-composing{border-bottom:2px solid}.custom-fields-component div.CodeMirror span.CodeMirror-matchingbracket{color:#0b0}.custom-fields-component div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#a22}.custom-fields-component .CodeMirror-matchingtag{background:#ff96004d}.custom-fields-component .CodeMirror-activeline-background{background:#e8f2ff}.custom-fields-component .CodeMirror{background:#fff;position:relative;overflow:hidden}.custom-fields-component .CodeMirror-scroll{z-index:0;outline:0;height:100%;margin-bottom:-50px;margin-right:-50px;padding-bottom:50px;position:relative;overflow:scroll!important}.custom-fields-component .CodeMirror-sizer{border-right:50px solid #0000;position:relative}.custom-fields-component .CodeMirror-gutter-filler,.custom-fields-component .CodeMirror-hscrollbar,.custom-fields-component .CodeMirror-scrollbar-filler,.custom-fields-component .CodeMirror-vscrollbar{z-index:6;outline:0;display:none;position:absolute}.custom-fields-component .CodeMirror-vscrollbar{top:0;right:0;overflow:hidden scroll}.custom-fields-component .CodeMirror-hscrollbar{bottom:0;left:0;overflow:scroll hidden}.custom-fields-component .CodeMirror-scrollbar-filler{bottom:0;right:0}.custom-fields-component .CodeMirror-gutter-filler{bottom:0;left:0}.custom-fields-component .CodeMirror-gutters{z-index:3;min-height:100%;position:absolute;top:0;left:0}.custom-fields-component .CodeMirror-gutter{white-space:normal;vertical-align:top;height:100%;margin-bottom:-50px;display:inline-block}.custom-fields-component .CodeMirror-gutter-wrapper{z-index:4;position:absolute;background:0 0!important;border:none!important}.custom-fields-component .CodeMirror-gutter-background{z-index:4;position:absolute;top:0;bottom:0}.custom-fields-component .CodeMirror-gutter-elt{cursor:default;z-index:4;position:absolute}.custom-fields-component .CodeMirror-gutter-wrapper ::selection{background-color:#0000}.custom-fields-component .CodeMirror-lines{cursor:text;min-height:1px}.custom-fields-component .CodeMirror pre.CodeMirror-line,.custom-fields-component .CodeMirror pre.CodeMirror-line-like{font-family:inherit;font-size:inherit;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:contextual;font-variant-ligatures:contextual;background:0 0;border-width:0;border-radius:0;margin:0;position:relative;overflow:visible}.custom-fields-component .CodeMirror-wrap pre.CodeMirror-line,.custom-fields-component .CodeMirror-wrap pre.CodeMirror-line-like{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.custom-fields-component .CodeMirror-linebackground{z-index:0;position:absolute;inset:0}.custom-fields-component .CodeMirror-linewidget{z-index:2;padding:.1px;position:relative}.custom-fields-component .CodeMirror-code{outline:0}.custom-fields-component .CodeMirror-gutter,.custom-fields-component .CodeMirror-gutters,.custom-fields-component .CodeMirror-linenumber,.custom-fields-component .CodeMirror-scroll,.custom-fields-component .CodeMirror-sizer{box-sizing:content-box}.custom-fields-component .CodeMirror-measure{visibility:hidden;width:100%;height:0;position:absolute;overflow:hidden}.custom-fields-component .CodeMirror-cursor{pointer-events:none;position:absolute}.custom-fields-component .CodeMirror-measure pre{position:static}.custom-fields-component div.CodeMirror-cursors{visibility:hidden;z-index:3;position:relative}.custom-fields-component .CodeMirror-focused div.CodeMirror-cursors,.custom-fields-component div.CodeMirror-dragcursors{visibility:visible}.custom-fields-component .CodeMirror-selected{background:#d9d9d9}.custom-fields-component .CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.custom-fields-component .CodeMirror-crosshair{cursor:crosshair}.custom-fields-component .CodeMirror-line::selection{background:#d7d4f0}.custom-fields-component .CodeMirror-line>span::selection{background:#d7d4f0}.custom-fields-component .CodeMirror-line>span>span::selection{background:#d7d4f0}.custom-fields-component .CodeMirror-line::-moz-selection,.custom-fields-component .CodeMirror-line>span::-moz-selection{background:#d7d4f0}.custom-fields-component .CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.custom-fields-component .cm-searching{background-color:#ff06}.custom-fields-component .cm-force-border{padding-right:.1px}@media print{.custom-fields-component .CodeMirror div.CodeMirror-cursors{visibility:hidden}}.custom-fields-component .cm-tab-wrap-hack:after{content:""}.custom-fields-component span.CodeMirror-selectedtext{background:0 0}.custom-fields-component .EasyMDEContainer{display:block}.custom-fields-component .CodeMirror-rtl pre{direction:rtl}.custom-fields-component .EasyMDEContainer.sided--no-fullscreen{flex-flow:wrap;display:flex}.custom-fields-component .EasyMDEContainer .CodeMirror{box-sizing:border-box;height:auto;font:inherit;z-index:0;word-wrap:break-word;border:1px solid #ced4da;border-bottom-right-radius:4px;border-bottom-left-radius:4px;padding:10px}.custom-fields-component .EasyMDEContainer .CodeMirror-scroll{cursor:text}.custom-fields-component .EasyMDEContainer .CodeMirror-fullscreen{z-index:8;background:#fff;height:auto;inset:50px 0 0;border-right:none!important;border-bottom-right-radius:0!important;position:fixed!important}.custom-fields-component .EasyMDEContainer .CodeMirror-sided{width:50%!important}.custom-fields-component .EasyMDEContainer.sided--no-fullscreen .CodeMirror-sided{border-bottom-right-radius:0;flex:auto;position:relative;border-right:none!important}.custom-fields-component .EasyMDEContainer .CodeMirror-placeholder{opacity:.5}.custom-fields-component .EasyMDEContainer .CodeMirror-focused .CodeMirror-selected{background:#d9d9d9}.custom-fields-component .editor-toolbar{-webkit-user-select:none;user-select:none;-o-user-select:none;border-top:1px solid #ced4da;border-left:1px solid #ced4da;border-right:1px solid #ced4da;border-top-left-radius:4px;border-top-right-radius:4px;padding:9px 10px;position:relative}.custom-fields-component .editor-toolbar.fullscreen{box-sizing:border-box;opacity:1;z-index:9;background:#fff;border:0;width:100%;height:50px;padding-top:10px;padding-bottom:10px;position:fixed;top:0;left:0}.custom-fields-component .editor-toolbar.fullscreen:before{background:-o-linear-gradient(270deg,#fff 0,#fff0 100%);background:-ms-linear-gradient(left,#fff 0,#fff0 100%);background:linear-gradient(90deg,#fff,#fff0);width:20px;height:50px;margin:0;padding:0;position:fixed;top:0;left:0}.custom-fields-component .editor-toolbar.fullscreen:after{background:-o-linear-gradient(270deg,#fff0 0,#fff 100%);background:-ms-linear-gradient(left,#fff0 0,#fff 100%);background:linear-gradient(90deg,#fff0,#fff);width:20px;height:50px;margin:0;padding:0;position:fixed;top:0;right:0}.custom-fields-component .EasyMDEContainer.sided--no-fullscreen .editor-toolbar{width:100%}.custom-fields-component .editor-toolbar .easymde-dropdown,.custom-fields-component .editor-toolbar button{text-align:center;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:3px;height:30px;margin:0;padding:0;display:inline-block;text-decoration:none!important}.custom-fields-component .editor-toolbar button{white-space:nowrap;min-width:30px;padding:0 6px;font-weight:700}.custom-fields-component .editor-toolbar button.active,.custom-fields-component .editor-toolbar button:hover{background:#fcfcfc;border-color:#95a5a6}.custom-fields-component .editor-toolbar i.separator{color:#0000;text-indent:-10px;border-left:1px solid #d9d9d9;border-right:1px solid #fff;width:0;margin:0 6px;display:inline-block}.custom-fields-component .editor-toolbar button:after{vertical-align:text-bottom;font-family:Arial,Helvetica Neue,Helvetica,sans-serif;font-size:65%;position:relative;top:2px}.custom-fields-component .editor-toolbar button.heading-1:after{content:"1"}.custom-fields-component .editor-toolbar button.heading-2:after{content:"2"}.custom-fields-component .editor-toolbar button.heading-3:after{content:"3"}.custom-fields-component .editor-toolbar button.heading-bigger:after{content:"ā–²"}.custom-fields-component .editor-toolbar button.heading-smaller:after{content:"ā–¼"}.custom-fields-component .editor-toolbar.disabled-for-preview button:not(.no-disable){opacity:.6;pointer-events:none}@media only screen and (max-width:700px){.custom-fields-component .editor-toolbar i.no-mobile{display:none}}.custom-fields-component .editor-statusbar{color:#959694;text-align:right;padding:8px 10px;font-size:12px}.custom-fields-component .EasyMDEContainer.sided--no-fullscreen .editor-statusbar{width:100%}.custom-fields-component .editor-statusbar span{min-width:4em;margin-left:1em;display:inline-block}.custom-fields-component .editor-statusbar .lines:before{content:"lines: "}.custom-fields-component .editor-statusbar .words:before{content:"words: "}.custom-fields-component .editor-statusbar .characters:before{content:"characters: "}.custom-fields-component .editor-preview-full{z-index:7;box-sizing:border-box;width:100%;height:100%;display:none;position:absolute;top:0;left:0;overflow:auto}.custom-fields-component .editor-preview-side{z-index:9;box-sizing:border-box;word-wrap:break-word;border:1px solid #ddd;width:50%;display:none;position:fixed;top:50px;bottom:0;right:0;overflow:auto}.custom-fields-component .editor-preview-active-side{display:block}.custom-fields-component .EasyMDEContainer.sided--no-fullscreen .editor-preview-active-side{flex:auto;height:auto;position:static}.custom-fields-component .editor-preview-active{display:block}.custom-fields-component .editor-preview{background:#fafafa;padding:10px}.custom-fields-component .editor-preview>p{margin-top:0}.custom-fields-component .editor-preview pre{background:#eee;margin-bottom:10px}.custom-fields-component .editor-preview table td,.custom-fields-component .editor-preview table th{border:1px solid #ddd;padding:5px}.custom-fields-component .cm-s-easymde .cm-tag{color:#63a35c}.custom-fields-component .cm-s-easymde .cm-attribute{color:#795da3}.custom-fields-component .cm-s-easymde .cm-string{color:#183691}.custom-fields-component .cm-s-easymde .cm-header-1{font-size:calc(1.375rem + 1.5vw)}.custom-fields-component .cm-s-easymde .cm-header-2{font-size:calc(1.325rem + .9vw)}.custom-fields-component .cm-s-easymde .cm-header-3{font-size:calc(1.3rem + .6vw)}.custom-fields-component .cm-s-easymde .cm-header-4{font-size:calc(1.275rem + .3vw)}.custom-fields-component .cm-s-easymde .cm-header-5{font-size:1.25rem}.custom-fields-component .cm-s-easymde .cm-header-6{font-size:1rem}.custom-fields-component .cm-s-easymde .cm-header-1,.custom-fields-component .cm-s-easymde .cm-header-2,.custom-fields-component .cm-s-easymde .cm-header-3,.custom-fields-component .cm-s-easymde .cm-header-4,.custom-fields-component .cm-s-easymde .cm-header-5,.custom-fields-component .cm-s-easymde .cm-header-6{margin-bottom:.5rem;line-height:1.2}.custom-fields-component .cm-s-easymde .cm-comment{background:#0000000d;border-radius:2px}.custom-fields-component .cm-s-easymde .cm-link{color:#7f8c8d}.custom-fields-component .cm-s-easymde .cm-url{color:#aab2b3}.custom-fields-component .cm-s-easymde .cm-quote{color:#7f8c8d;font-style:italic}.custom-fields-component .editor-toolbar .easymde-dropdown{border:1px solid #fff;border-radius:0;position:relative}.custom-fields-component .editor-toolbar .easymde-dropdown,.custom-fields-component .editor-toolbar .easymde-dropdown:hover{background:linear-gradient(to bottom right,#fff 0 84%,#333 50% 100%)}.custom-fields-component .easymde-dropdown-content{visibility:hidden;z-index:2;background-color:#f9f9f9;padding:8px;display:block;position:absolute;top:30px;box-shadow:0 8px 16px #0003}.custom-fields-component .easymde-dropdown:active .easymde-dropdown-content,.custom-fields-component .easymde-dropdown:focus .easymde-dropdown-content,.custom-fields-component .easymde-dropdown:focus-within .easymde-dropdown-content{visibility:visible}.custom-fields-component .easymde-dropdown-content button{display:block}.custom-fields-component span[data-img-src]:after{content:"";background-image:var(--bg-image);max-width:100%;height:0;max-height:100%;padding-top:var(--height);width:var(--width);background-repeat:no-repeat;background-size:contain;display:block}.custom-fields-component .CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){background:#ff000026}.custom-fields-component .cropper-container{-webkit-touch-callout:none;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;user-select:none;direction:ltr;font-size:0;line-height:0;position:relative}.custom-fields-component .cropper-container img{backface-visibility:hidden;image-orientation:0deg;width:100%;height:100%;display:block;min-width:0!important;max-width:none!important;min-height:0!important;max-height:none!important}.custom-fields-component .cropper-canvas,.custom-fields-component .cropper-crop-box,.custom-fields-component .cropper-drag-box,.custom-fields-component .cropper-modal,.custom-fields-component .cropper-wrap-box{position:absolute;inset:0}.custom-fields-component .cropper-canvas,.custom-fields-component .cropper-wrap-box{overflow:hidden}.custom-fields-component .cropper-drag-box{opacity:0;background-color:#fff}.custom-fields-component .cropper-modal{opacity:.5;background-color:#000}.custom-fields-component .cropper-view-box{outline:1px solid #3399ffbf;width:100%;height:100%;display:block;overflow:hidden}.custom-fields-component .cropper-dashed{opacity:.5;border:0 dashed #eee;display:block;position:absolute}.custom-fields-component .cropper-dashed.dashed-h{border-top-width:1px;border-bottom-width:1px;width:100%;height:33.3333%;top:33.3333%;left:0}.custom-fields-component .cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;width:33.3333%;height:100%;top:0;left:33.3333%}.custom-fields-component .cropper-center{opacity:.75;width:0;height:0;display:block;position:absolute;top:50%;left:50%}.custom-fields-component .cropper-center:after,.custom-fields-component .cropper-center:before{content:" ";background-color:#eee;display:block;position:absolute}.custom-fields-component .cropper-center:before{width:7px;height:1px;top:0;left:-3px}.custom-fields-component .cropper-center:after{width:1px;height:7px;top:-3px;left:0}.custom-fields-component .cropper-face,.custom-fields-component .cropper-line,.custom-fields-component .cropper-point{opacity:.1;width:100%;height:100%;display:block;position:absolute}.custom-fields-component .cropper-face{background-color:#fff;top:0;left:0}.custom-fields-component .cropper-line{background-color:#39f}.custom-fields-component .cropper-line.line-e{cursor:ew-resize;width:5px;top:0;right:-3px}.custom-fields-component .cropper-line.line-n{cursor:ns-resize;height:5px;top:-3px;left:0}.custom-fields-component .cropper-line.line-w{cursor:ew-resize;width:5px;top:0;left:-3px}.custom-fields-component .cropper-line.line-s{cursor:ns-resize;height:5px;bottom:-3px;left:0}.custom-fields-component .cropper-point{opacity:.75;background-color:#39f;width:5px;height:5px}.custom-fields-component .cropper-point.point-e{cursor:ew-resize;margin-top:-3px;top:50%;right:-3px}.custom-fields-component .cropper-point.point-n{cursor:ns-resize;margin-left:-3px;top:-3px;left:50%}.custom-fields-component .cropper-point.point-w{cursor:ew-resize;margin-top:-3px;top:50%;left:-3px}.custom-fields-component .cropper-point.point-s{cursor:s-resize;margin-left:-3px;bottom:-3px;left:50%}.custom-fields-component .cropper-point.point-ne{cursor:nesw-resize;top:-3px;right:-3px}.custom-fields-component .cropper-point.point-nw{cursor:nwse-resize;top:-3px;left:-3px}.custom-fields-component .cropper-point.point-sw{cursor:nesw-resize;bottom:-3px;left:-3px}.custom-fields-component .cropper-point.point-se{cursor:nwse-resize;opacity:1;width:20px;height:20px;bottom:-3px;right:-3px}@media (min-width:768px){.custom-fields-component .cropper-point.point-se{width:15px;height:15px}}@media (min-width:992px){.custom-fields-component .cropper-point.point-se{width:10px;height:10px}}@media (min-width:1200px){.custom-fields-component .cropper-point.point-se{opacity:.75;width:5px;height:5px}}.custom-fields-component .cropper-point.point-se:before{content:" ";opacity:0;background-color:#39f;width:200%;height:200%;display:block;position:absolute;bottom:-50%;right:-50%}.custom-fields-component .cropper-invisible{opacity:0}.custom-fields-component .cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.custom-fields-component .cropper-hide{width:0;height:0;display:block;position:absolute}.custom-fields-component .cropper-hidden{display:none!important}.custom-fields-component .cropper-move{cursor:move}.custom-fields-component .cropper-crop{cursor:crosshair}.custom-fields-component .cropper-disabled .cropper-drag-box,.custom-fields-component .cropper-disabled .cropper-face,.custom-fields-component .cropper-disabled .cropper-line,.custom-fields-component .cropper-disabled .cropper-point{cursor:not-allowed}.custom-fields-component .filepond--assistant{clip:rect(1px,1px,1px,1px);clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;padding:0;position:absolute;overflow:hidden}.custom-fields-component .filepond--browser.filepond--browser{opacity:0;width:calc(100% - 2em);margin:0;padding:0;font-size:0;position:absolute;top:1.75em;left:1em}.custom-fields-component .filepond--data{visibility:hidden;pointer-events:none;contain:strict;border:none;width:0;height:0;margin:0;padding:0;position:absolute}.custom-fields-component .filepond--drip{opacity:.1;pointer-events:none;background:#00000003;border-radius:.5em;position:absolute;inset:0;overflow:hidden}.custom-fields-component .filepond--drip-blob{transform-origin:50%;background:#292625;border-radius:50%;width:8em;height:8em;margin-top:-4em;margin-left:-4em}.custom-fields-component .filepond--drip-blob,.custom-fields-component .filepond--drop-label{will-change:transform,opacity;position:absolute;top:0;left:0}.custom-fields-component .filepond--drop-label{color:#4f4f4f;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;height:0;margin:0;display:flex;right:0}.custom-fields-component .filepond--drop-label.filepond--drop-label label{margin:0;padding:.5em;display:block}.custom-fields-component .filepond--drop-label label{cursor:default;text-align:center;font-size:.875em;font-weight:400;line-height:1.5}.custom-fields-component .filepond--label-action{-webkit-text-decoration-skip:ink;-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto;cursor:pointer;-webkit-text-decoration:underline #a7a4a4;text-decoration:underline #a7a4a4}.custom-fields-component .filepond--root[data-disabled] .filepond--drop-label label{opacity:.5}.custom-fields-component .filepond--file-action-button.filepond--file-action-button{width:1.625em;height:1.625em;font-family:inherit;font-size:1em;line-height:inherit;will-change:transform,opacity;border:none;outline:none;margin:0;padding:0}.custom-fields-component .filepond--file-action-button.filepond--file-action-button span{clip:rect(1px,1px,1px,1px);clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;padding:0;position:absolute;overflow:hidden}.custom-fields-component .filepond--file-action-button.filepond--file-action-button svg{width:100%;height:100%}.custom-fields-component .filepond--file-action-button.filepond--file-action-button:after{content:"";position:absolute;inset:-.75em}.custom-fields-component .filepond--file-action-button{cursor:auto;color:#fff;background-color:#00000080;background-image:none;border-radius:50%;transition:box-shadow .25s ease-in;box-shadow:0 0 #fff0}.custom-fields-component .filepond--file-action-button:focus,.custom-fields-component .filepond--file-action-button:hover{box-shadow:0 0 0 .125em #ffffffe6}.custom-fields-component .filepond--file-action-button[disabled]{color:#ffffff80;background-color:#00000040}.custom-fields-component .filepond--file-action-button[hidden]{display:none}.custom-fields-component .filepond--file-info{will-change:transform,opacity;pointer-events:none;-webkit-user-select:none;user-select:none;flex-direction:column;flex:1;align-items:flex-start;min-width:0;margin:0 .5em 0 0;display:flex;position:static}.custom-fields-component .filepond--file-info *{margin:0}.custom-fields-component .filepond--file-info .filepond--file-info-main{text-overflow:ellipsis;white-space:nowrap;width:100%;font-size:.75em;line-height:1.2;overflow:hidden}.custom-fields-component .filepond--file-info .filepond--file-info-sub{opacity:.5;white-space:nowrap;font-size:.625em;transition:opacity .25s ease-in-out}.custom-fields-component .filepond--file-info .filepond--file-info-sub:empty{display:none}.custom-fields-component .filepond--file-status{text-align:right;will-change:transform,opacity;pointer-events:none;-webkit-user-select:none;user-select:none;flex-direction:column;flex-grow:0;flex-shrink:0;align-items:flex-end;min-width:2.25em;margin:0;display:flex;position:static}.custom-fields-component .filepond--file-status *{white-space:nowrap;margin:0}.custom-fields-component .filepond--file-status .filepond--file-status-main{font-size:.75em;line-height:1.2}.custom-fields-component .filepond--file-status .filepond--file-status-sub{opacity:.5;font-size:.625em;transition:opacity .25s ease-in-out}.custom-fields-component .filepond--file-wrapper.filepond--file-wrapper{border:none;min-width:0;height:100%;margin:0;padding:0}.custom-fields-component .filepond--file-wrapper.filepond--file-wrapper>legend{clip:rect(1px,1px,1px,1px);clip-path:inset(50%);white-space:nowrap;border:0;width:1px;height:1px;padding:0;position:absolute;overflow:hidden}.custom-fields-component .filepond--file{color:#fff;border-radius:.5em;align-items:flex-start;height:100%;padding:.5625em;display:flex;position:static}.custom-fields-component .filepond--file .filepond--file-status{margin-left:auto;margin-right:2.25em}.custom-fields-component .filepond--file .filepond--processing-complete-indicator{pointer-events:none;-webkit-user-select:none;user-select:none;z-index:3}.custom-fields-component .filepond--file .filepond--file-action-button,.custom-fields-component .filepond--file .filepond--processing-complete-indicator,.custom-fields-component .filepond--file .filepond--progress-indicator{position:absolute}.custom-fields-component .filepond--file [data-align*=left]{left:.5625em}.custom-fields-component .filepond--file [data-align*=right]{right:.5625em}.custom-fields-component .filepond--file [data-align*=center]{left:calc(50% - .8125em)}.custom-fields-component .filepond--file [data-align*=bottom]{bottom:1.125em}.custom-fields-component .filepond--file [data-align=center]{top:calc(50% - .8125em)}.custom-fields-component .filepond--file .filepond--progress-indicator{margin-top:.1875em}.custom-fields-component .filepond--file .filepond--progress-indicator[data-align*=right]{margin-right:.1875em}.custom-fields-component .filepond--file .filepond--progress-indicator[data-align*=left]{margin-left:.1875em}.custom-fields-component [data-filepond-item-state*=error] .filepond--file-info,.custom-fields-component [data-filepond-item-state*=invalid] .filepond--file-info,.custom-fields-component [data-filepond-item-state=cancelled] .filepond--file-info{margin-right:2.25em}.custom-fields-component [data-filepond-item-state~=processing] .filepond--file-status-sub{opacity:0}.custom-fields-component [data-filepond-item-state~=processing] .filepond--action-abort-item-processing~.filepond--file-status .filepond--file-status-sub{opacity:.5}.custom-fields-component [data-filepond-item-state=processing-error] .filepond--file-status-sub{opacity:0}.custom-fields-component [data-filepond-item-state=processing-error] .filepond--action-retry-item-processing~.filepond--file-status .filepond--file-status-sub{opacity:.5}.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing svg{animation:fall .5s linear .125s both}.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--file-status-sub{opacity:.5}.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--file-info-sub,.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--processing-complete-indicator:not([style*=hidden])~.filepond--file-status .filepond--file-status-sub{opacity:0}.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--action-revert-item-processing~.filepond--file-info .filepond--file-info-sub{opacity:.5}.custom-fields-component [data-filepond-item-state*=error] .filepond--file-wrapper,.custom-fields-component [data-filepond-item-state*=error] .filepond--panel,.custom-fields-component [data-filepond-item-state*=invalid] .filepond--file-wrapper,.custom-fields-component [data-filepond-item-state*=invalid] .filepond--panel{animation:shake .65s linear both}.custom-fields-component [data-filepond-item-state*=busy] .filepond--progress-indicator svg{animation:spin 1s linear infinite}@keyframes shake{10%,90%{transform:translate(-.0625em)}20%,80%{transform:translate(.125em)}30%,50%,70%{transform:translate(-.25em)}40%,60%{transform:translate(.25em)}}@keyframes fall{0%{opacity:0;animation-timing-function:ease-out;transform:scale(.5)}70%{opacity:1;animation-timing-function:ease-in-out;transform:scale(1.1)}to{animation-timing-function:ease-out;transform:scale(1)}}.custom-fields-component .filepond--hopper[data-hopper-state=drag-over]>*{pointer-events:none}.custom-fields-component .filepond--hopper[data-hopper-state=drag-over]:after{content:"";z-index:100;position:absolute;inset:0}.custom-fields-component .filepond--progress-indicator{z-index:103}.custom-fields-component .filepond--file-action-button{z-index:102}.custom-fields-component .filepond--file-status{z-index:101}.custom-fields-component .filepond--file-info{z-index:100}.custom-fields-component .filepond--item{z-index:1;will-change:transform,opacity;touch-action:auto;margin:.25em;padding:0;position:absolute;top:0;left:0;right:0}.custom-fields-component .filepond--item>.filepond--panel{z-index:-1}.custom-fields-component .filepond--item>.filepond--panel .filepond--panel-bottom{box-shadow:0 .0625em .125em -.0625em #00000040}.custom-fields-component .filepond--item>.filepond--file-wrapper,.custom-fields-component .filepond--item>.filepond--panel{transition:opacity .15s ease-out}.custom-fields-component .filepond--item[data-drag-state]{cursor:-webkit-grab;cursor:grab}.custom-fields-component .filepond--item[data-drag-state]>.filepond--panel{transition:box-shadow .125s ease-in-out;box-shadow:0 0 #0000}.custom-fields-component .filepond--item[data-drag-state=drag]{cursor:-webkit-grabbing;cursor:grabbing}.custom-fields-component .filepond--item[data-drag-state=drag]>.filepond--panel{box-shadow:0 .125em .3125em #00000053}.custom-fields-component .filepond--item[data-drag-state]:not([data-drag-state=idle]){z-index:2}.custom-fields-component .filepond--item-panel{background-color:#64605e}.custom-fields-component [data-filepond-item-state=processing-complete] .filepond--item-panel{background-color:#369763}.custom-fields-component [data-filepond-item-state*=error] .filepond--item-panel,.custom-fields-component [data-filepond-item-state*=invalid] .filepond--item-panel{background-color:#c44e47}.custom-fields-component .filepond--item-panel{border-radius:.5em;transition:background-color .25s}.custom-fields-component .filepond--list-scroller{will-change:transform;margin:0;position:absolute;top:0;left:0;right:0}.custom-fields-component .filepond--list-scroller[data-state=overflow] .filepond--list{bottom:0;right:0}.custom-fields-component .filepond--list-scroller[data-state=overflow]{-webkit-overflow-scrolling:touch;overflow:hidden scroll;-webkit-mask:linear-gradient(#000 calc(100% - .5em),#0000);mask:linear-gradient(#000 calc(100% - .5em),#0000)}.custom-fields-component .filepond--list-scroller::-webkit-scrollbar{background:0 0}.custom-fields-component .filepond--list-scroller::-webkit-scrollbar:vertical{width:1em}.custom-fields-component .filepond--list-scroller::-webkit-scrollbar:horizontal{height:0}.custom-fields-component .filepond--list-scroller::-webkit-scrollbar-thumb{background-color:#0000004d;background-clip:content-box;border:.3125em solid #0000;border-radius:99999px}.custom-fields-component .filepond--list.filepond--list{will-change:transform;margin:0;padding:0;list-style-type:none;position:absolute;top:0}.custom-fields-component .filepond--list{left:.75em;right:.75em}.custom-fields-component .filepond--root[data-style-panel-layout~=integrated]{width:100%;max-width:none;height:100%;margin:0}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--panel-root,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--panel-root{border-radius:0}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--panel-root>*,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--panel-root>*{display:none}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--drop-label,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--drop-label{z-index:7;justify-content:center;align-items:center;height:auto;display:flex;bottom:0}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--item-panel,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--item-panel{display:none}.custom-fields-component .filepond--root[data-style-panel-layout~=compact] .filepond--list-scroller,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--list-scroller{height:100%;margin-top:0;margin-bottom:0;overflow:hidden}.custom-fields-component .filepond--root[data-style-panel-layout~=compact] .filepond--list,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--list{height:100%;left:0;right:0}.custom-fields-component .filepond--root[data-style-panel-layout~=compact] .filepond--item,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--item{margin:0}.custom-fields-component .filepond--root[data-style-panel-layout~=compact] .filepond--file-wrapper,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--file-wrapper{height:100%}.custom-fields-component .filepond--root[data-style-panel-layout~=compact] .filepond--drop-label,.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--drop-label{z-index:7}.custom-fields-component .filepond--root[data-style-panel-layout~=circle]{border-radius:99999rem;overflow:hidden}.custom-fields-component .filepond--root[data-style-panel-layout~=circle]>.filepond--panel{border-radius:inherit}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--file-info,.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--file-status,.custom-fields-component .filepond--root[data-style-panel-layout~=circle]>.filepond--panel>*{display:none}@media not all and (min-resolution:.001dpcm){@supports ((-webkit-appearance:none)) and (stroke-color:transparent){.custom-fields-component .filepond--root[data-style-panel-layout~=circle]{will-change:transform}}}.custom-fields-component .filepond--panel-root{background-color:#f1f0ef;border-radius:.5em}.custom-fields-component .filepond--panel{pointer-events:none;margin:0;position:absolute;top:0;left:0;right:0;height:100%!important}.custom-fields-component .filepond-panel:not([data-scalable=false]){height:auto!important}.custom-fields-component .filepond--panel[data-scalable=false]>div{display:none}.custom-fields-component .filepond--panel[data-scalable=true]{-webkit-transform-style:preserve-3d;transform-style:preserve-3d;background-color:#0000!important;border:none!important}.custom-fields-component .filepond--panel-bottom,.custom-fields-component .filepond--panel-center,.custom-fields-component .filepond--panel-top{margin:0;padding:0;position:absolute;top:0;left:0;right:0}.custom-fields-component .filepond--panel-bottom,.custom-fields-component .filepond--panel-top{height:.5em}.custom-fields-component .filepond--panel-top{border-bottom:none!important;border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.custom-fields-component .filepond--panel-top:after{content:"";background-color:inherit;height:2px;position:absolute;bottom:-1px;left:0;right:0}.custom-fields-component .filepond--panel-bottom,.custom-fields-component .filepond--panel-center{will-change:transform;backface-visibility:hidden;transform-origin:0 0;transform:translateY(.5em)}.custom-fields-component .filepond--panel-bottom{border-top:none!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.custom-fields-component .filepond--panel-bottom:before{content:"";background-color:inherit;height:2px;position:absolute;top:-1px;left:0;right:0}.custom-fields-component .filepond--panel-center{border-top:none!important;border-bottom:none!important;border-radius:0!important;height:100px!important}.custom-fields-component .filepond--panel-center:not([style]){visibility:hidden}.custom-fields-component .filepond--progress-indicator{color:#fff;pointer-events:none;will-change:transform,opacity;width:1.25em;height:1.25em;margin:0;position:static}.custom-fields-component .filepond--progress-indicator svg{vertical-align:top;transform-box:fill-box;width:100%;height:100%}.custom-fields-component .filepond--progress-indicator path{fill:none;stroke:currentColor}.custom-fields-component .filepond--list-scroller{z-index:6}.custom-fields-component .filepond--drop-label{z-index:5}.custom-fields-component .filepond--drip{z-index:3}.custom-fields-component .filepond--root>.filepond--panel{z-index:2}.custom-fields-component .filepond--browser{z-index:1}.custom-fields-component .filepond--root{box-sizing:border-box;text-align:left;text-rendering:optimizelegibility;contain:layout style size;direction:ltr;margin-bottom:1em;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:1rem;font-weight:450;line-height:normal;position:relative}.custom-fields-component .filepond--root *{box-sizing:inherit;line-height:inherit}.custom-fields-component .filepond--root :not(text){font-size:inherit}.custom-fields-component .filepond--root[data-disabled]{pointer-events:none}.custom-fields-component .filepond--root[data-disabled] .filepond--list-scroller{pointer-events:all}.custom-fields-component .filepond--root[data-disabled] .filepond--list{pointer-events:none}.custom-fields-component .filepond--root .filepond--drop-label{min-height:4.75em}.custom-fields-component .filepond--root .filepond--list-scroller{margin-top:1em;margin-bottom:1em}.custom-fields-component .filepond--root .filepond--credits{opacity:.4;color:inherit;z-index:3;font-size:11px;line-height:.85;text-decoration:none;position:absolute;bottom:-14px;right:0}.custom-fields-component .filepond--root .filepond--credits[style]{margin-top:14px;top:0;bottom:auto}.custom-fields-component .filepond--action-edit-item.filepond--action-edit-item{width:2em;height:2em;padding:.1875em}.custom-fields-component .filepond--action-edit-item.filepond--action-edit-item[data-align*=center]{margin-left:-.1875em}.custom-fields-component .filepond--action-edit-item.filepond--action-edit-item[data-align*=bottom]{margin-bottom:-.1875em}.custom-fields-component .filepond--action-edit-item-alt{line-height:inherit;color:inherit;pointer-events:all;background:0 0;border:none;outline:none;margin:0 0 0 .25em;padding:0;font-family:inherit;position:absolute}.custom-fields-component .filepond--action-edit-item-alt svg{width:1.3125em;height:1.3125em}.custom-fields-component .filepond--action-edit-item-alt span{opacity:0;font-size:0}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--action-edit-item{opacity:1!important;visibility:visible!important}.custom-fields-component .filepond--image-preview-markup{position:absolute;top:0;left:0}.custom-fields-component .filepond--image-preview-wrapper{z-index:2}.custom-fields-component .filepond--image-preview-overlay{opacity:0;z-index:2;pointer-events:none;-webkit-user-select:none;user-select:none;width:100%;min-height:5rem;max-height:7rem;margin:0;display:block;position:absolute;top:0;left:0}.custom-fields-component .filepond--image-preview-overlay svg{width:100%;height:auto;color:inherit;max-height:inherit}.custom-fields-component .filepond--image-preview-overlay-idle{mix-blend-mode:multiply;color:#282828d9}.custom-fields-component .filepond--image-preview-overlay-success{mix-blend-mode:normal;color:#369763}.custom-fields-component .filepond--image-preview-overlay-failure{mix-blend-mode:normal;color:#c44e47}@supports (-webkit-marquee-repetition:infinite) and ((-o-object-fit:fill) or (object-fit:fill)){.custom-fields-component .filepond--image-preview-overlay-idle{mix-blend-mode:normal}}.custom-fields-component .filepond--image-preview-wrapper{-webkit-user-select:none;user-select:none;background:#00000003;border-radius:.45em;height:100%;margin:0;position:absolute;top:0;left:0;right:0;overflow:hidden}.custom-fields-component .filepond--image-preview{z-index:1;pointer-events:none;will-change:transform,opacity;background:#222;align-items:center;width:100%;height:100%;display:flex;position:absolute;top:0;left:0}.custom-fields-component .filepond--image-clip{margin:0 auto;position:relative;overflow:hidden}.custom-fields-component .filepond--image-clip[data-transparency-indicator=grid] canvas,.custom-fields-component .filepond--image-clip[data-transparency-indicator=grid] img{background-color:#fff;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22%23eee%22%20viewBox%3D%220%200%20100%20100%22%3E%3Cpath%20d%3D%22M0%200h50v50H0M50%2050h50v50H50%22%2F%3E%3C%2Fsvg%3E");background-size:1.25em 1.25em}.custom-fields-component .filepond--image-bitmap,.custom-fields-component .filepond--image-vector{will-change:transform;position:absolute;top:0;left:0}.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview-wrapper{border-radius:0}.custom-fields-component .filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview{justify-content:center;align-items:center;height:100%;display:flex}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-wrapper{border-radius:99999rem}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-overlay{top:auto;bottom:0;transform:scaleY(-1)}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--file .filepond--file-action-button[data-align*=bottom]:not([data-align*=center]){margin-bottom:.325em}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=left]{left:calc(50% - 3em)}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=right]{right:calc(50% - 3em)}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=left],.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=right]{margin-bottom:.5125em}.custom-fields-component .filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=center]{margin-top:0;margin-bottom:.1875em;margin-left:.1875em}.custom-fields-component .filepond--media-preview audio{display:none}.custom-fields-component .filepond--media-preview .audioplayer{width:calc(100% - 1.4em);margin:2.3em auto auto}.custom-fields-component .filepond--media-preview .playpausebtn{float:left;cursor:pointer;background-position:50%;background-repeat:no-repeat;border:none;border-radius:25px;outline:none;width:25px;height:25px;margin-top:.3em;margin-right:.3em}.custom-fields-component .filepond--media-preview .playpausebtn:hover{background-color:#00000080}.custom-fields-component .filepond--media-preview .play{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAyElEQVQ4T9XUsWoCQRRG4XPaFL5SfIy8gKYKBCysrax8Ahs7qzQ2qVIFOwsrsbEWLEK6EBFGBrIQhN2d3dnGgalm+Jh7789Ix8uOPe4YDCH0gZ66atKW0pJDCE/AEngDXtRjCpwCRucbGANzNVTBqWBhfAJDdV+GNgWj8wtM41bPt3AbsDB2f69d/0dzwC0wUDe54A8wAWbqJbfkD+BZPeQO5QsYqYu6LKb0MIb7VT3VYfG8CnwEHtT3FKi4c8e/TZMyk3LYFrwCgMdHFbRDKS8AAAAASUVORK5CYII=)}.custom-fields-component .filepond--media-preview .pause{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAh0lEQVQ4T+2UsQkCURBE30PLMbAMMResQrAPsQ0TK9AqDKxGZeTLD74aGNwlhzfZssvADDMrPcOe+RggYZIJcG2s2KinMidZAvu6u6uzT8u+JCeZArfmcKUeK+EaONTdQy23bxgJX8aPHvIHsSnVuzTx36rn2pQFsGuqN//ZlK7vbIDvq6vkJ9yteBXzecYbAAAAAElFTkSuQmCC)}.custom-fields-component .filepond--media-preview .timeline{float:left;background:#ffffff4d;border-radius:15px;width:calc(100% - 2.5em);height:3px;margin-top:1em}.custom-fields-component .filepond--media-preview .playhead{background:#fff;border-radius:50%;width:13px;height:13px;margin-top:-5px}.custom-fields-component .filepond--media-preview-wrapper{pointer-events:auto;background:#00000003;border-radius:.45em;height:100%;margin:0;position:absolute;top:0;left:0;right:0;overflow:hidden}.custom-fields-component .filepond--media-preview-wrapper:before{content:" ";width:100%;height:2em;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#000000",endColorstr="#00000000",GradientType=0);z-index:3;background:linear-gradient(#000,#0000);position:absolute}.custom-fields-component .filepond--media-preview{z-index:1;transform-origin:50%;will-change:transform,opacity;width:100%;height:100%;display:block;position:relative}.custom-fields-component .filepond--media-preview audio,.custom-fields-component .filepond--media-preview video{will-change:transform;width:100%}.custom-fields-component .noUi-target,.custom-fields-component .noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:#0000;-webkit-user-select:none;user-select:none;-ms-touch-action:none;touch-action:none;box-sizing:border-box}.custom-fields-component .noUi-target{position:relative}.custom-fields-component .noUi-base,.custom-fields-component .noUi-connects{z-index:1;width:100%;height:100%;position:relative}.custom-fields-component .noUi-connects{z-index:0;overflow:hidden}.custom-fields-component .noUi-connect,.custom-fields-component .noUi-origin{will-change:transform;z-index:1;transform-origin:0 0;width:100%;height:100%;-webkit-transform-style:preserve-3d;transform-style:flat;position:absolute;top:0;right:0}.custom-fields-component .noUi-txt-dir-rtl.noUi-horizontal .noUi-origin{left:0;right:auto}.custom-fields-component .noUi-vertical .noUi-origin{width:0;top:-100%}.custom-fields-component .noUi-horizontal .noUi-origin{height:0}.custom-fields-component .noUi-handle{backface-visibility:hidden;position:absolute}.custom-fields-component .noUi-touch-area{width:100%;height:100%}.custom-fields-component .noUi-state-tap .noUi-connect,.custom-fields-component .noUi-state-tap .noUi-origin{transition:transform .3s}.custom-fields-component .noUi-state-drag *{cursor:inherit!important}.custom-fields-component .noUi-horizontal{height:18px}.custom-fields-component .noUi-horizontal .noUi-handle{width:34px;height:28px;top:-6px;right:-17px}.custom-fields-component .noUi-vertical{width:18px}.custom-fields-component .noUi-vertical .noUi-handle{width:28px;height:34px;bottom:-17px;right:-6px}.custom-fields-component .noUi-txt-dir-rtl.noUi-horizontal .noUi-handle{left:-17px;right:auto}.custom-fields-component .noUi-target{background:#fafafa;border:1px solid #d3d3d3;border-radius:4px;box-shadow:inset 0 1px 1px #f0f0f0,0 3px 6px -5px #bbb}.custom-fields-component .noUi-connects{border-radius:3px}.custom-fields-component .noUi-connect{background:#3fb8af}.custom-fields-component .noUi-draggable{cursor:ew-resize}.custom-fields-component .noUi-vertical .noUi-draggable{cursor:ns-resize}.custom-fields-component .noUi-handle{cursor:default;background:#fff;border:1px solid #d9d9d9;border-radius:3px;box-shadow:inset 0 0 1px #fff,inset 0 1px 7px #ebebeb,0 3px 6px -3px #bbb}.custom-fields-component .noUi-active{box-shadow:inset 0 0 1px #fff,inset 0 1px 7px #ddd,0 3px 6px -3px #bbb}.custom-fields-component .noUi-handle:after,.custom-fields-component .noUi-handle:before{content:"";background:#e8e7e6;width:1px;height:14px;display:block;position:absolute;top:6px;left:14px}.custom-fields-component .noUi-handle:after{left:17px}.custom-fields-component .noUi-vertical .noUi-handle:after,.custom-fields-component .noUi-vertical .noUi-handle:before{width:14px;height:1px;top:14px;left:6px}.custom-fields-component .noUi-vertical .noUi-handle:after{top:17px}.custom-fields-component [disabled] .noUi-connect{background:#b8b8b8}.custom-fields-component [disabled] .noUi-handle,.custom-fields-component [disabled].noUi-handle,.custom-fields-component [disabled].noUi-target{cursor:not-allowed}.custom-fields-component .noUi-pips,.custom-fields-component .noUi-pips *{box-sizing:border-box}.custom-fields-component .noUi-pips{color:#999;position:absolute}.custom-fields-component .noUi-value{white-space:nowrap;text-align:center;position:absolute}.custom-fields-component .noUi-value-sub{color:#ccc;font-size:10px}.custom-fields-component .noUi-marker{background:#ccc;position:absolute}.custom-fields-component .noUi-marker-large,.custom-fields-component .noUi-marker-sub{background:#aaa}.custom-fields-component .noUi-pips-horizontal{width:100%;height:80px;padding:10px 0;top:100%;left:0}.custom-fields-component .noUi-value-horizontal{transform:translate(-50%,50%)}.custom-fields-component .noUi-rtl .noUi-value-horizontal{transform:translate(50%,50%)}.custom-fields-component .noUi-marker-horizontal.noUi-marker{width:2px;height:5px;margin-left:-1px}.custom-fields-component .noUi-marker-horizontal.noUi-marker-sub{height:10px}.custom-fields-component .noUi-marker-horizontal.noUi-marker-large{height:15px}.custom-fields-component .noUi-pips-vertical{height:100%;padding:0 10px;top:0;left:100%}.custom-fields-component .noUi-value-vertical{padding-left:25px;transform:translateY(-50%)}.custom-fields-component .noUi-rtl .noUi-value-vertical{transform:translateY(50%)}.custom-fields-component .noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.custom-fields-component .noUi-marker-vertical.noUi-marker-sub{width:10px}.custom-fields-component .noUi-marker-vertical.noUi-marker-large{width:15px}.custom-fields-component .noUi-tooltip{color:#000;text-align:center;white-space:nowrap;background:#fff;border:1px solid #d9d9d9;border-radius:3px;padding:5px;display:block;position:absolute}.custom-fields-component .noUi-horizontal .noUi-tooltip{bottom:120%;left:50%;transform:translate(-50%)}.custom-fields-component .noUi-vertical .noUi-tooltip{top:50%;right:120%;transform:translateY(-50%)}.custom-fields-component .noUi-horizontal .noUi-origin>.noUi-tooltip{bottom:10px;left:auto;transform:translate(50%)}.custom-fields-component .noUi-vertical .noUi-origin>.noUi-tooltip{top:auto;right:28px;transform:translateY(-18px)}.custom-fields-component .fi-fo-builder{row-gap:calc(var(--spacing)*4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.custom-fields-component .fi-fo-builder .fi-fo-builder-actions{column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-builder .fi-fo-builder-actions.fi-hidden{display:none}.custom-fields-component .fi-fo-builder .fi-fo-builder-items{grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.custom-fields-component .fi-fo-builder .fi-fo-builder-items>*+*{margin-top:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item{border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-item{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-item:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-item:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-item:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-item:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-item.fi-collapsed .fi-fo-builder-item-header-collapsible-actions{rotate:-180deg}.custom-fields-component .fi-fo-builder .fi-fo-builder-item.fi-collapsed .fi-fo-builder-item-header-collapse-action,.custom-fields-component .fi-fo-builder .fi-fo-builder-item:not(.fi-collapsed) .fi-fo-builder-item-header-expand-action{pointer-events:none;opacity:0}.custom-fields-component .fi-fo-builder.fi-fo-builder-not-contained>.fi-fo-builder-items>.fi-fo-builder-item{--tw-shadow:0 0 #0000;--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000;border-radius:0}.custom-fields-component .fi-fo-builder.fi-fo-builder-not-contained>.fi-fo-builder-items>.fi-fo-builder-item>.fi-fo-builder-item-content{padding:0}.custom-fields-component .fi-fo-builder.fi-fo-builder-not-contained>.fi-fo-builder-items>.fi-fo-builder-label-between-items-ctn>.fi-fo-builder-label-between-items-divider-before{width:0}.custom-fields-component .fi-fo-builder.fi-fo-builder-not-contained>.fi-fo-builder-items>.fi-fo-builder-label-between-items-ctn>.fi-fo-builder-label-between-items{padding-inline-start:0}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header{align-items:center;column-gap:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);display:flex;overflow:hidden}.custom-fields-component .fi-fo-builder.fi-collapsible .fi-fo-builder-item-header{cursor:pointer;-webkit-user-select:none;user-select:none}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-start-actions{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-icon{color:var(--gray-400);flex-shrink:0}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-label.fi-truncated{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-end-actions{align-items:center;column-gap:calc(var(--spacing)*3);margin-inline-start:auto;display:flex}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-collapsible-actions{position:relative}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-collapse-action,.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-collapsible-actions,.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-expand-action{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-header-expand-action{position:absolute;inset:0;rotate:180deg}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-content:not(.fi-fo-builder-item-content-has-preview){padding:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-content.fi-fo-builder-item-content-has-preview{position:relative}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-has-header>.fi-fo-builder-item-content{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-100)}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-has-header>.fi-fo-builder-item-content:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-item-has-header>.fi-fo-builder-item-content:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-preview:not(.fi-interactive){pointer-events:none}.custom-fields-component .fi-fo-builder .fi-fo-builder-item-preview-edit-overlay{z-index:1;cursor:pointer;position:absolute;inset:0}.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker-ctn{border-radius:var(--radius-lg);background-color:var(--color-white)}.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker-ctn:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items-ctn{pointer-events:none;visibility:hidden;opacity:0;width:100%;height:0;transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));justify-content:center;margin-top:0;display:flex;position:relative;overflow:visible}.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items-ctn:focus-within,.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items-ctn:has(+.fi-fo-builder-item:hover),.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items-ctn:hover,.custom-fields-component .fi-fo-builder .fi-fo-builder-item:hover+.fi-fo-builder-add-between-items-ctn{pointer-events:auto;visibility:visible;opacity:1}.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items{z-index:10;--tw-translate-y:calc(-50% + 0.5rem);translate:var(--tw-translate-x) var(--tw-translate-y);border-radius:var(--radius-lg);background-color:var(--color-white);position:absolute;top:50%}.custom-fields-component .fi-fo-builder .fi-fo-builder-add-between-items:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-ctn{margin-top:var(--spacing);margin-bottom:calc(var(--spacing)*-3);align-items:center;display:flex;position:relative}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-before{width:calc(var(--spacing)*3);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-200);flex-shrink:0}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-before:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-before:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items{padding-inline:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-500);flex-shrink:0}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-after{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-200);flex:1}.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-after:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-builder .fi-fo-builder-label-between-items-divider-after:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker{justify-content:center;display:flex}.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker.fi-align-left,.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker.fi-align-end,.custom-fields-component .fi-fo-builder .fi-fo-builder-block-picker.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-search-input-wrp{margin-bottom:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-actions{margin-bottom:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-options{gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-options.fi-grid-direction-col{margin-top:calc(var(--spacing)*-4)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-options.fi-grid-direction-col .fi-fo-checkbox-list-option-ctn{break-inside:avoid;padding-top:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option{column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-checkbox-input{margin-top:var(--spacing);flex-shrink:0}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-fo-checkbox-list-option-text{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);display:grid}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-fo-checkbox-list-option-label{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);overflow-wrap:break-word;color:var(--gray-950);overflow:hidden}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-fo-checkbox-list-option-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-fo-checkbox-list-option-description{color:var(--gray-500)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option .fi-fo-checkbox-list-option-description:where(.dark,.dark *),.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option:has(.fi-checkbox-input:disabled) .fi-fo-checkbox-list-option-label{color:var(--gray-400)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option:has(.fi-checkbox-input:disabled) .fi-fo-checkbox-list-option-label:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option:has(.fi-checkbox-input:disabled) .fi-fo-checkbox-list-option-description{color:var(--gray-300)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-option:has(.fi-checkbox-input:disabled) .fi-fo-checkbox-list-option-description:where(.dark,.dark *){color:var(--gray-600)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-no-search-results-message{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-fo-checkbox-list .fi-fo-checkbox-list-no-search-results-message:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-code-editor{overflow:hidden}.custom-fields-component .fi-fo-code-editor .cm-editor.cm-focused{--tw-outline-style:none!important;outline-style:none!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters{min-height:calc(var(--spacing)*48)!important;border-inline-end-color:var(--gray-300)!important;background-color:var(--gray-100)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters:where(.dark,.dark *){border-inline-end-color:var(--gray-800)!important;background-color:var(--gray-950)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters .cm-gutter.cm-lineNumbers .cm-gutterElement{border-start-start-radius:var(--radius-md);border-end-start-radius:var(--radius-md);margin-inline-start:var(--spacing)}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters .cm-gutter.cm-lineNumbers .cm-gutterElement.cm-activeLineGutter{background-color:var(--gray-200)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters .cm-gutter.cm-lineNumbers .cm-gutterElement.cm-activeLineGutter:where(.dark,.dark *){background-color:var(--gray-800)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters .cm-gutter.cm-foldGutter .cm-gutterElement.cm-activeLineGutter{background-color:var(--gray-200)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-gutters .cm-gutter.cm-foldGutter .cm-gutterElement.cm-activeLineGutter:where(.dark,.dark *){background-color:var(--gray-800)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-scroller{min-height:calc(var(--spacing)*48)!important}.custom-fields-component .fi-fo-code-editor .cm-editor .cm-line{border-start-end-radius:var(--radius-md);border-end-end-radius:var(--radius-md);margin-inline-end:var(--spacing)}.custom-fields-component .fi-fo-code-editor.fi-disabled .cm-editor .cm-gutters .cm-gutter.cm-foldGutter .cm-gutterElement.cm-activeLineGutter,.custom-fields-component .fi-fo-code-editor.fi-disabled .cm-editor .cm-gutters .cm-gutter.cm-lineNumbers .cm-gutterElement.cm-activeLineGutter,.custom-fields-component .fi-fo-code-editor.fi-disabled .cm-editor .cm-line.cm-activeLine{background-color:#0000!important}.custom-fields-component .fi-fo-color-picker .fi-input-wrp-content{display:flex}.custom-fields-component .fi-fo-color-picker .fi-fo-color-picker-preview{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);-webkit-user-select:none;user-select:none;border-radius:3.40282e+38px;flex-shrink:0;margin-block:auto;margin-inline-end:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-color-picker .fi-fo-color-picker-preview.fi-empty{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-200);--tw-ring-inset:inset}.custom-fields-component .fi-fo-color-picker .fi-fo-color-picker-preview.fi-empty:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-color-picker .fi-fo-color-picker-preview.fi-empty:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-fo-color-picker .fi-fo-color-picker-panel{z-index:10;border-radius:var(--radius-lg);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);display:none;position:absolute}.custom-fields-component .fi-fo-date-time-picker input::-webkit-datetime-edit{padding:0;display:block}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-trigger{width:100%}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input{--tw-border-style:none;width:100%;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);color:var(--gray-950);--tw-outline-style:none;background-color:#0000;border-style:none;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input::placeholder{color:var(--gray-400)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input:disabled{color:var(--gray-500);-webkit-text-fill-color:var(--color-gray-500)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input:where(.dark,.dark *)::placeholder{color:var(--gray-500)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-display-text-input:where(.dark,.dark *):disabled{color:var(--gray-400);-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel{z-index:10;position:absolute}.custom-fields-component :where(.fi-fo-date-time-picker .fi-fo-date-time-picker-panel>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing)*3*var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing)*3*(1 - var(--tw-space-y-reverse)))}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel{border-radius:var(--radius-lg);background-color:var(--color-white);padding:calc(var(--spacing)*4);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-panel .fi-fo-date-time-picker-panel-header{justify-content:space-between;align-items:center;display:flex}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-month-select{cursor:pointer;--tw-border-style:none;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950);background-color:#0000;border-style:none;flex-grow:1;padding:0}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-month-select:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-month-select:where(.dark,.dark *){background-color:var(--gray-900);color:var(--color-white)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-year-input{width:calc(var(--spacing)*16);--tw-border-style:none;text-align:right;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-950);background-color:#0000;border-style:none;padding:0}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-year-input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-year-input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar-header{gap:var(--spacing);grid-template-columns:repeat(7,minmax(0,1fr));display:grid}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar-header .fi-fo-date-time-picker-calendar-header-day{text-align:center;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-500)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar-header .fi-fo-date-time-picker-calendar-header-day:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar{grid-template-columns:repeat(7,minmax(calc(var(--spacing)*7),1fr));gap:var(--spacing);display:grid}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day{text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:var(--leading-loose);line-height:var(--leading-loose);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;border-radius:3.40282e+38px;transition-duration:75ms}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-disabled{pointer-events:none;opacity:.5}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day:not(.fi-disabled){cursor:pointer}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-selected{background-color:var(--gray-50);color:var(--primary-600)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-selected:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-selected:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-selected:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-focused:not(.fi-selected):not(.fi-disabled){background-color:var(--gray-100)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-focused:not(.fi-selected):not(.fi-disabled):where(.dark,.dark *){background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-focused:not(.fi-selected):not(.fi-disabled):where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-fo-date-time-picker-calendar-day-today:not(.fi-focused):not(.fi-selected):not(.fi-disabled){color:var(--primary-600)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day.fi-fo-date-time-picker-calendar-day-today:not(.fi-focused):not(.fi-selected):not(.fi-disabled):where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day:not(.fi-fo-date-time-picker-calendar-day-today):not(.fi-selected){color:var(--gray-950)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-calendar .fi-fo-date-time-picker-calendar-day:not(.fi-fo-date-time-picker-calendar-day-today):not(.fi-selected):where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs{justify-content:center;align-items:center;display:flex}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs:where(:dir(rtl),[dir=rtl],[dir=rtl] *){flex-direction:row-reverse}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs input{width:calc(var(--spacing)*10);--tw-border-style:none;text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-950);background-color:#0000;border-style:none;margin-inline-end:var(--spacing);padding:0}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs input:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs input:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs .fi-fo-date-time-picker-time-input-separator{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-500)}.custom-fields-component .fi-fo-date-time-picker .fi-fo-date-time-picker-time-inputs .fi-fo-date-time-picker-time-input-separator:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-field{row-gap:calc(var(--spacing)*2);display:grid}@media (min-width:40rem){.custom-fields-component .fi-fo-field.fi-fo-field-has-inline-label{align-items:flex-start;column-gap:calc(var(--spacing)*4);grid-template-columns:repeat(3,minmax(0,1fr))}.custom-fields-component .fi-fo-field.fi-fo-field-has-inline-label .fi-fo-field-content-col{grid-column:span 2/span 2}}.custom-fields-component .fi-fo-field .fi-fo-field-label,.custom-fields-component .fi-fo-field .fi-fo-field-label-ctn{align-items:flex-start;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component :is(.fi-fo-field .fi-fo-field-label-ctn,.fi-fo-field .fi-fo-field-label)>.fi-checkbox-input{margin-top:calc(var(--spacing)*.5);flex-shrink:0}.custom-fields-component :is(.fi-fo-field .fi-fo-field-label-ctn,.fi-fo-field .fi-fo-field-label)>.fi-toggle{margin-block:calc(var(--spacing)*-.5)}.custom-fields-component :is(.fi-fo-field .fi-fo-field-label-ctn,.fi-fo-field .fi-fo-field-label)>.fi-sc:first-child{flex-grow:0}.custom-fields-component :is(.fi-fo-field .fi-fo-field-label-ctn,.fi-fo-field .fi-fo-field-label).fi-hidden{display:none}.custom-fields-component .fi-fo-field .fi-fo-field-label-content{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-fo-field .fi-fo-field-label-content:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-field .fi-fo-field-label-content .fi-fo-field-label-required-mark{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--danger-600)}.custom-fields-component .fi-fo-field .fi-fo-field-label-content .fi-fo-field-label-required-mark:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-fo-field .fi-fo-field-label-col{row-gap:calc(var(--spacing)*2);grid-auto-columns:minmax(0,1fr);height:100%;display:grid}@media (min-width:40rem){.custom-fields-component .fi-fo-field .fi-fo-field-label-col.fi-vertical-align-start{align-items:flex-start}.custom-fields-component .fi-fo-field .fi-fo-field-label-col.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-fo-field .fi-fo-field-label-col.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-fo-field .fi-fo-field-content-col{row-gap:calc(var(--spacing)*2);grid-auto-columns:minmax(0,1fr);display:grid}.custom-fields-component .fi-fo-field .fi-fo-field-content-ctn{align-items:center;column-gap:calc(var(--spacing)*3);width:100%;display:flex}.custom-fields-component .fi-fo-field .fi-fo-field-content{width:100%}.custom-fields-component .fi-fo-field .fi-fo-field-wrp-error-message{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--danger-600)}.custom-fields-component .fi-fo-field .fi-fo-field-wrp-error-message:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-fo-field .fi-fo-field-wrp-error-list{list-style-type:disc;list-style-position:inside}.custom-fields-component :where(.fi-fo-field .fi-fo-field-wrp-error-list>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing)*.5*var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing)*.5*(1 - var(--tw-space-y-reverse)))}.custom-fields-component .fi-fo-file-upload{row-gap:calc(var(--spacing)*2);flex-direction:column;display:flex}.custom-fields-component .fi-fo-file-upload.fi-align-left,.custom-fields-component .fi-fo-file-upload.fi-align-start{align-items:flex-start}.custom-fields-component .fi-fo-file-upload.fi-align-center{align-items:center}.custom-fields-component .fi-fo-file-upload.fi-align-end,.custom-fields-component .fi-fo-file-upload.fi-align-right{align-items:flex-end}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-input-ctn{width:100%;height:100%}.custom-fields-component .fi-fo-file-upload.fi-fo-file-upload-avatar .fi-fo-file-upload-input-ctn{height:100%;width:calc(var(--spacing)*32)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-error-message{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--danger-600)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-error-message:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-fo-file-upload .filepond--root{border-radius:var(--radius-lg);background-color:var(--color-white);font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);margin-bottom:0;overflow:hidden}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--root{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-fo-file-upload .filepond--root:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--root:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-file-upload .filepond--root:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--root:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-fo-file-upload .filepond--root[data-disabled=disabled]{background-color:var(--gray-50)}.custom-fields-component .fi-fo-file-upload .filepond--root[data-disabled=disabled]:where(.dark,.dark *){--tw-ring-color:#ffffff1a;background-color:#0000}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--root[data-disabled=disabled]:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-fo-file-upload .filepond--root[data-style-panel-layout=compact\ circle]{border-radius:3.40282e+38px}.custom-fields-component .fi-fo-file-upload .filepond--panel-root{background-color:#0000}.custom-fields-component .fi-fo-file-upload .filepond--drop-label{height:auto!important;padding:calc(var(--spacing)*3)!important}.custom-fields-component .fi-fo-file-upload .filepond--drop-label label{padding:0!important}.custom-fields-component .fi-fo-file-upload .filepond--drop-label,.custom-fields-component .fi-fo-file-upload .filepond--drop-label label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-600)!important;opacity:1!important}.custom-fields-component :is(.fi-fo-file-upload .filepond--drop-label,.fi-fo-file-upload .filepond--drop-label label):where(.dark,.dark *){color:var(--gray-400)!important}.custom-fields-component .fi-fo-file-upload .filepond--label-action{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--primary-700);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;text-decoration-line:none;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-fo-file-upload .filepond--label-action:hover{color:var(--primary-600)}}.custom-fields-component .fi-fo-file-upload .filepond--label-action:where(.dark,.dark *){color:var(--primary-400)}@media (hover:hover){.custom-fields-component .fi-fo-file-upload .filepond--label-action:where(.dark,.dark *):hover{color:var(--primary-400)}}.custom-fields-component .fi-fo-file-upload .filepond--drip-blob{background-color:var(--gray-400)}.custom-fields-component .fi-fo-file-upload .filepond--drip-blob:where(.dark,.dark *){background-color:var(--gray-500)}.custom-fields-component .fi-fo-file-upload .filepond--root[data-style-panel-layout=grid] .filepond--item{width:calc(50% - .5rem);display:inline}@media (min-width:64rem){.custom-fields-component .fi-fo-file-upload .filepond--root[data-style-panel-layout=grid] .filepond--item{width:calc(33.33% - .5rem)}}.custom-fields-component .fi-fo-file-upload .filepond--download-icon{pointer-events:auto;width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);background-color:var(--color-white);vertical-align:bottom;margin-inline-end:var(--spacing);display:inline-block}@media (hover:hover){.custom-fields-component .fi-fo-file-upload .filepond--download-icon:hover{background-color:#ffffffb3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--download-icon:hover{background-color:color-mix(in oklab,var(--color-white) 70%,transparent)}}}.custom-fields-component .fi-fo-file-upload .filepond--download-icon{-webkit-mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWRvd25sb2FkIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0yMSAxNXY0YTIgMiAwIDAgMS0yIDJINWEyIDIgMCAwIDEtMi0ydi00TTcgMTBsNSA1IDUtNU0xMiAxNVYzIi8+PC9zdmc+);mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBjbGFzcz0iZmVhdGhlciBmZWF0aGVyLWRvd25sb2FkIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0yMSAxNXY0YTIgMiAwIDAgMS0yIDJINWEyIDIgMCAwIDEtMi0ydi00TTcgMTBsNSA1IDUtNU0xMiAxNVYzIi8+PC9zdmc+);-webkit-mask-size:100%;mask-size:100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.custom-fields-component .fi-fo-file-upload .filepond--open-icon{pointer-events:auto;width:calc(var(--spacing)*4);height:calc(var(--spacing)*4);background-color:var(--color-white);vertical-align:bottom;margin-inline-end:var(--spacing);display:inline-block}@media (hover:hover){.custom-fields-component .fi-fo-file-upload .filepond--open-icon:hover{background-color:#ffffffb3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--open-icon:hover{background-color:color-mix(in oklab,var(--color-white) 70%,transparent)}}}.custom-fields-component .fi-fo-file-upload .filepond--open-icon{-webkit-mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiIGNsYXNzPSJoLTYgdy02IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTEwIDZINmEyIDIgMCAwIDAtMiAydjEwYTIgMiAwIDAgMCAyIDJoMTBhMiAyIDAgMCAwIDItMnYtNE0xNCA0aDZtMCAwdjZtMC02TDEwIDE0Ii8+PC9zdmc+);mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2Utd2lkdGg9IjIiIGNsYXNzPSJoLTYgdy02IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTEwIDZINmEyIDIgMCAwIDAtMiAydjEwYTIgMiAwIDAgMCAyIDJoMTBhMiAyIDAgMCAwIDItMnYtNE0xNCA0aDZtMCAwdjZtMC02TDEwIDE0Ii8+PC9zdmc+);-webkit-mask-size:100%;mask-size:100%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.custom-fields-component .fi-fo-file-upload .filepond--file-action-button.filepond--action-edit-item{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .filepond--file-action-button.filepond--action-edit-item{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor{isolation:isolate;z-index:50;width:100vw;height:100dvh;padding:calc(var(--spacing)*2);position:fixed;inset:0}@media (min-width:40rem){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor{padding:calc(var(--spacing)*10)}}@media (min-width:48rem){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor{padding:calc(var(--spacing)*20)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-overlay{cursor:pointer;background-color:var(--gray-950);width:100%;height:100%;position:fixed;inset:0}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-overlay{background-color:color-mix(in oklab,var(--gray-950) 50%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-overlay:where(.dark,.dark *){background-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-overlay:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-950) 75%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-overlay{will-change:transform}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-window{isolation:isolate;border-radius:var(--radius-xl);background-color:var(--color-white);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);width:100%;height:100%;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-900);flex-direction:column;margin-inline:auto;display:flex;overflow:hidden}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-window{--tw-ring-color:color-mix(in oklab, var(--gray-900) 10%, transparent)}}@media (min-width:64rem){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-window{flex-direction:row}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-window:where(.dark,.dark *){background-color:var(--gray-800);--tw-ring-color:var(--gray-50)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-window:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--gray-50) 10%, transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-image-ctn{margin:calc(var(--spacing)*4);flex:1;max-width:100%;max-height:100%}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-image{width:auto;height:100%}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel{background-color:var(--gray-50);flex-direction:column;flex:1;width:100%;height:100%;display:flex;overflow-y:auto}@media (min-width:64rem){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel{max-width:var(--container-xs)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900) 30%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-main{flex:1}.custom-fields-component :where(.fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-main>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing)*6*var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing)*6*(1 - var(--tw-space-y-reverse)))}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-main{padding:calc(var(--spacing)*4);overflow:auto}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group{gap:calc(var(--spacing)*3);display:grid}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group .fi-btn-group{width:100%}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group .fi-btn.fi-active{background-color:var(--gray-50)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group .fi-btn.fi-active:where(.dark,.dark *){background-color:var(--gray-700)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group .fi-fo-file-upload-editor-control-panel-group-title{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--gray-950)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-group .fi-fo-file-upload-editor-control-panel-group-title:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-footer{align-items:center;gap:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .fi-fo-file-upload-editor-control-panel .fi-fo-file-upload-editor-control-panel-reset-action{margin-left:auto}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .cropper-drag-box.cropper-crop.cropper-modal{background-color:var(--gray-100)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .cropper-drag-box.cropper-crop.cropper-modal{background-color:color-mix(in oklab,var(--gray-100) 50%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .cropper-drag-box.cropper-crop.cropper-modal{opacity:1}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .cropper-drag-box.cropper-crop.cropper-modal:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor .cropper-drag-box.cropper-crop.cropper-modal:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900) 80%,transparent)}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-circle-cropper .cropper-face,.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-circle-cropper .cropper-view-box{border-radius:50%}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-crop-only .fi-fo-file-upload-editor-window{max-width:var(--container-3xl);flex-direction:column}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-crop-only .fi-fo-file-upload-editor-image-ctn{flex:1;min-height:0;overflow:hidden}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-crop-only .fi-fo-file-upload-editor-control-panel{flex:none;height:auto}@media (min-width:64rem){.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-crop-only .fi-fo-file-upload-editor-control-panel{max-width:none}}.custom-fields-component .fi-fo-file-upload .fi-fo-file-upload-editor.fi-fo-file-upload-editor-crop-only .fi-fo-file-upload-editor-control-panel-footer{justify-content:flex-start}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table-ctn>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table{table-layout:auto;width:100%}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>thead>tr>th{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);text-align:start;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-700)}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>thead>tr>th:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>thead>tr>th.fi-has-action{width:calc(var(--spacing)*9);padding:0}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody>tr>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody>tr:where(:dir(rtl),[dir=rtl],[dir=rtl] *)>:not(:last-child)){--tw-divide-x-reverse:1}.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody>tr:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-key-value .fi-fo-key-value-table>tbody>tr:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>tbody>tr>td{width:50%;padding:0}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>tbody>tr>td.fi-has-action{width:auto;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>tbody>tr>td.fi-has-action .fi-fo-key-value-table-row-sortable-handle{display:flex}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-table>tbody>tr>td .fi-input{font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.custom-fields-component .fi-fo-key-value .fi-fo-key-value-add-action-ctn{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);justify-content:center;display:flex}@media (min-width:40rem){.custom-fields-component .fi-fo-key-value-wrp.fi-fo-field-has-inline-label .fi-fo-field-label-col{padding-top:calc(var(--spacing)*1.5)}}.custom-fields-component .fi-fo-markdown-editor{--color-cm-red:#991b1b;--color-cm-orange:#9a3412;--color-cm-amber:#92400e;--color-cm-yellow:#854d0e;--color-cm-lime:#3f6212;--color-cm-green:#166534;--color-cm-emerald:#065f46;--color-cm-teal:#115e59;--color-cm-cyan:#155e75;--color-cm-sky:#075985;--color-cm-blue:#1e40af;--color-cm-indigo:#3730a3;--color-cm-violet:#5b21b6;--color-cm-purple:#6b21a8;--color-cm-fuchsia:#86198f;--color-cm-pink:#9d174d;--color-cm-rose:#9f1239;--color-cm-gray:#18181b;--color-cm-gray-muted:#71717a;--color-cm-gray-background:#e4e4e7}.custom-fields-component .fi-fo-markdown-editor:not(.fi-disabled){max-width:100%;font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-950);overflow:hidden}.custom-fields-component .fi-fo-markdown-editor:not(.fi-disabled):where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-markdown-editor.fi-disabled{border-radius:var(--radius-lg);background-color:var(--gray-50);width:100%;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);display:block}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor.fi-disabled{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-fo-markdown-editor.fi-disabled:where(.dark,.dark *){color:var(--gray-400);--tw-ring-color:#ffffff1a;background-color:#0000}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor.fi-disabled:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .CodeMirror{padding-inline:calc(var(--spacing)*4)!important;padding-block:calc(var(--spacing)*3)!important}.custom-fields-component .fi-fo-markdown-editor .cm-s-easymde .cm-comment{color:var(--color-cm-gray-muted);background-color:#0000}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .CodeMirror-cursor{border-color:currentColor}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-keyword{color:var(--color-cm-violet)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-atom{color:var(--color-cm-blue)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-number{color:var(--color-cm-green)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-def{color:var(--color-cm-blue)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-variable{color:var(--color-cm-yellow)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-variable-2{color:var(--color-cm-blue)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-variable-3{color:var(--color-cm-emerald)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-operator,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-property{color:var(--color-cm-gray)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-string,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-string-2{color:var(--color-cm-rose)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-meta{color:var(--color-cm-gray-muted)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-error{color:var(--color-cm-red)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-qualifier{color:var(--color-cm-gray-muted)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-builtin{color:var(--color-cm-violet)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-bracket,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-hr{color:var(--color-cm-gray-muted)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-quote{color:var(--color-cm-sky)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-quote~.cm-quote{color:var(--color-cm-gray-muted)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-list,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-list~.cm-keyword,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-list~.cm-variable-2,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-list~.cm-variable-3,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-tab~.cm-keyword,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-tab~.cm-variable-2,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-tab~.cm-variable-3{color:var(--color-cm-gray)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-link{color:var(--color-cm-blue)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-tag{color:var(--color-cm-red)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-attribute{color:var(--color-cm-amber)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-attribute+.cm-string{color:var(--color-cm-green)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-code+.cm-comment:not(.cm-formatting-code){background-color:var(--color-cm-gray-background);color:var(--color-cm-gray)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-1{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-2{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-3{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-4{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-5{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-header-6{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-comment{background-image:none}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-formatting-code-block,.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .cm-s-easymde .cm-tab~.cm-comment{color:inherit;background-color:#0000}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .CodeMirror{--tw-border-style:none;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);color:inherit;background-color:#0000;border-style:none}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .CodeMirror-scroll{height:auto}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar{gap:var(--spacing);border-style:var(--tw-border-style);border-width:0;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*2);border-radius:0;flex-wrap:wrap;display:flex}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8);border-radius:var(--radius-lg);--tw-border-style:none;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;border-style:none;place-content:center;padding:0;transition-duration:75ms;display:grid!important}@media (hover:hover){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:focus-visible{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button.active{background-color:var(--gray-50)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button.active:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button.active:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button:before{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);background-color:var(--gray-700);content:"";display:block;-webkit-mask-position:50%;mask-position:50%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar button.active:before{background-color:var(--primary-600)}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .separator{width:var(--spacing);--tw-border-style:none;border-style:none;margin:0!important}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .bold:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M4%203a1%201%200%200%201%201-1h6a4.5%204.5%200%200%201%203.274%207.587A4.75%204.75%200%200%201%2011.25%2018H5a1%201%200%200%201-1-1zm2.5%205.5v-4H11a2%202%200%201%201%200%204zm0%202.5v4.5h4.75a2.25%202.25%200%200%200%200-4.5z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M4%203a1%201%200%200%201%201-1h6a4.5%204.5%200%200%201%203.274%207.587A4.75%204.75%200%200%201%2011.25%2018H5a1%201%200%200%201-1-1zm2.5%205.5v-4H11a2%202%200%201%201%200%204zm0%202.5v4.5h4.75a2.25%202.25%200%200%200%200-4.5z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .italic:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M8%202.75A.75.75%200%200%201%208.75%202h7.5a.75.75%200%200%201%200%201.5h-3.215l-4.483%2013h2.698a.75.75%200%200%201%200%201.5h-7.5a.75.75%200%200%201%200-1.5h3.215l4.483-13H8.75A.75.75%200%200%201%208%202.75%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M8%202.75A.75.75%200%200%201%208.75%202h7.5a.75.75%200%200%201%200%201.5h-3.215l-4.483%2013h2.698a.75.75%200%200%201%200%201.5h-7.5a.75.75%200%200%201%200-1.5h3.215l4.483-13H8.75A.75.75%200%200%201%208%202.75%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .strikethrough:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M11.617%203.963c-1.186-.318-2.418-.323-3.416.015-.992.336-1.49.91-1.642%201.476s-.007%201.313.684%202.1c.528.6%201.273%201.1%202.128%201.446h7.879a.75.75%200%200%201%200%201.5H2.75a.75.75%200%200%201%200-1.5h3.813a6%206%200%200%201-.447-.456C5.18%207.479%204.798%206.231%205.11%205.066c.312-1.164%201.268-2.055%202.61-2.509%201.336-.451%202.877-.42%204.286-.043.856.23%201.684.592%202.409%201.074a.75.75%200%201%201-.83%201.25%206.7%206.7%200%200%200-1.968-.875m1.909%208.123a.75.75%200%200%201%201.015.309c.53.99.607%202.062.18%203.01-.421.94-1.289%201.648-2.441%202.038-1.336.452-2.877.42-4.286.043s-2.759-1.121-3.69-2.18a.75.75%200%201%201%201.127-.99c.696.791%201.765%201.403%202.952%201.721%201.186.318%202.418.323%203.416-.015.853-.288%201.34-.756%201.555-1.232.21-.467.205-1.049-.136-1.69a.75.75%200%200%201%20.308-1.014%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M11.617%203.963c-1.186-.318-2.418-.323-3.416.015-.992.336-1.49.91-1.642%201.476s-.007%201.313.684%202.1c.528.6%201.273%201.1%202.128%201.446h7.879a.75.75%200%200%201%200%201.5H2.75a.75.75%200%200%201%200-1.5h3.813a6%206%200%200%201-.447-.456C5.18%207.479%204.798%206.231%205.11%205.066c.312-1.164%201.268-2.055%202.61-2.509%201.336-.451%202.877-.42%204.286-.043.856.23%201.684.592%202.409%201.074a.75.75%200%201%201-.83%201.25%206.7%206.7%200%200%200-1.968-.875m1.909%208.123a.75.75%200%200%201%201.015.309c.53.99.607%202.062.18%203.01-.421.94-1.289%201.648-2.441%202.038-1.336.452-2.877.42-4.286.043s-2.759-1.121-3.69-2.18a.75.75%200%201%201%201.127-.99c.696.791%201.765%201.403%202.952%201.721%201.186.318%202.418.323%203.416-.015.853-.288%201.34-.756%201.555-1.232.21-.467.205-1.049-.136-1.69a.75.75%200%200%201%20.308-1.014%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .link:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20d%3D%22M12.232%204.232a2.5%202.5%200%200%201%203.536%203.536l-1.225%201.224a.75.75%200%200%200%201.061%201.06l1.224-1.224a4%204%200%200%200-5.656-5.656l-3%203a4%204%200%200%200%20.225%205.865.75.75%200%200%200%20.977-1.138%202.5%202.5%200%200%201-.142-3.667z%22%2F%3E%3Cpath%20d%3D%22M11.603%207.963a.75.75%200%200%200-.977%201.138%202.5%202.5%200%200%201%20.142%203.667l-3%203a2.5%202.5%200%200%201-3.536-3.536l1.225-1.224a.75.75%200%200%200-1.061-1.06l-1.224%201.224a4%204%200%201%200%205.656%205.656l3-3a4%204%200%200%200-.225-5.865%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20d%3D%22M12.232%204.232a2.5%202.5%200%200%201%203.536%203.536l-1.225%201.224a.75.75%200%200%200%201.061%201.06l1.224-1.224a4%204%200%200%200-5.656-5.656l-3%203a4%204%200%200%200%20.225%205.865.75.75%200%200%200%20.977-1.138%202.5%202.5%200%200%201-.142-3.667z%22%2F%3E%3Cpath%20d%3D%22M11.603%207.963a.75.75%200%200%200-.977%201.138%202.5%202.5%200%200%201%20.142%203.667l-3%203a2.5%202.5%200%200%201-3.536-3.536l1.225-1.224a.75.75%200%200%200-1.061-1.06l-1.224%201.224a4%204%200%201%200%205.656%205.656l3-3a4%204%200%200%200-.225-5.865%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .heading:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M7%2012h10M7%205v14M17%205v14M15%2019h4M15%205h4M5%2019h4M5%205h4%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20stroke%3D%22currentColor%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cpath%20d%3D%22M7%2012h10M7%205v14M17%205v14M15%2019h4M15%205h4M5%2019h4M5%205h4%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .quote:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M10%202c-2.236%200-4.43.18-6.57.524C1.993%202.755%201%204.014%201%205.426v5.148c0%201.413.993%202.67%202.43%202.902q1.753.283%203.55.414c.28.02.521.18.642.413l1.713%203.293a.75.75%200%200%200%201.33%200l1.713-3.293a.78.78%200%200%201%20.642-.413%2041%2041%200%200%200%203.55-.414c1.437-.231%202.43-1.49%202.43-2.902V5.426c0-1.413-.993-2.67-2.43-2.902A41%2041%200%200%200%2010%202M6.75%206a.75.75%200%200%200%200%201.5h6.5a.75.75%200%200%200%200-1.5zm0%202.5a.75.75%200%200%200%200%201.5h3.5a.75.75%200%200%200%200-1.5z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M10%202c-2.236%200-4.43.18-6.57.524C1.993%202.755%201%204.014%201%205.426v5.148c0%201.413.993%202.67%202.43%202.902q1.753.283%203.55.414c.28.02.521.18.642.413l1.713%203.293a.75.75%200%200%200%201.33%200l1.713-3.293a.78.78%200%200%201%20.642-.413%2041%2041%200%200%200%203.55-.414c1.437-.231%202.43-1.49%202.43-2.902V5.426c0-1.413-.993-2.67-2.43-2.902A41%2041%200%200%200%2010%202M6.75%206a.75.75%200%200%200%200%201.5h6.5a.75.75%200%200%200%200-1.5zm0%202.5a.75.75%200%200%200%200%201.5h3.5a.75.75%200%200%200%200-1.5z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .code:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M6.28%205.22a.75.75%200%200%201%200%201.06L2.56%2010l3.72%203.72a.75.75%200%200%201-1.06%201.06L.97%2010.53a.75.75%200%200%201%200-1.06l4.25-4.25a.75.75%200%200%201%201.06%200m7.44%200a.75.75%200%200%201%201.06%200l4.25%204.25a.75.75%200%200%201%200%201.06l-4.25%204.25a.75.75%200%200%201-1.06-1.06L17.44%2010l-3.72-3.72a.75.75%200%200%201%200-1.06m-2.343-3.209a.75.75%200%200%201%20.612.867l-2.5%2014.5a.75.75%200%200%201-1.478-.255l2.5-14.5a.75.75%200%200%201%20.866-.612%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M6.28%205.22a.75.75%200%200%201%200%201.06L2.56%2010l3.72%203.72a.75.75%200%200%201-1.06%201.06L.97%2010.53a.75.75%200%200%201%200-1.06l4.25-4.25a.75.75%200%200%201%201.06%200m7.44%200a.75.75%200%200%201%201.06%200l4.25%204.25a.75.75%200%200%201%200%201.06l-4.25%204.25a.75.75%200%200%201-1.06-1.06L17.44%2010l-3.72-3.72a.75.75%200%200%201%200-1.06m-2.343-3.209a.75.75%200%200%201%20.612.867l-2.5%2014.5a.75.75%200%200%201-1.478-.255l2.5-14.5a.75.75%200%200%201%20.866-.612%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .unordered-list:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M6%204.75A.75.75%200%200%201%206.75%204h10.5a.75.75%200%200%201%200%201.5H6.75A.75.75%200%200%201%206%204.75M6%2010a.75.75%200%200%201%20.75-.75h10.5a.75.75%200%200%201%200%201.5H6.75A.75.75%200%200%201%206%2010m0%205.25a.75.75%200%200%201%20.75-.75h10.5a.75.75%200%200%201%200%201.5H6.75a.75.75%200%200%201-.75-.75M1.99%204.75a1%201%200%200%201%201-1H3a1%201%200%200%201%201%201v.01a1%201%200%200%201-1%201h-.01a1%201%200%200%201-1-1zm0%2010.5a1%201%200%200%201%201-1H3a1%201%200%200%201%201%201v.01a1%201%200%200%201-1%201h-.01a1%201%200%200%201-1-1zm0-5.25a1%201%200%200%201%201-1H3a1%201%200%200%201%201%201v.01a1%201%200%200%201-1%201h-.01a1%201%200%200%201-1-1z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M6%204.75A.75.75%200%200%201%206.75%204h10.5a.75.75%200%200%201%200%201.5H6.75A.75.75%200%200%201%206%204.75M6%2010a.75.75%200%200%201%20.75-.75h10.5a.75.75%200%200%201%200%201.5H6.75A.75.75%200%200%201%206%2010m0%205.25a.75.75%200%200%201%20.75-.75h10.5a.75.75%200%200%201%200%201.5H6.75a.75.75%200%200%201-.75-.75M1.99%204.75a1%201%200%200%201%201-1H3a1%201%200%200%201%201%201v.01a1%201%200%200%201-1%201h-.01a1%201%200%200%201-1-1zm0%2010.5a1%201%200%200%201%201-1H3a1%201%200%200%201%201%201v.01a1%201%200%200%201-1%201h-.01a1%201%200%200%201-1-1zm0-5.25a1%201%200%200%201%201-1H3a1%201%200%200%201%201%201v.01a1%201%200%200%201-1%201h-.01a1%201%200%200%201-1-1z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .ordered-list:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20d%3D%22M3%201.25a.75.75%200%200%200%200%201.5h.25v2.5a.75.75%200%200%200%201.5%200V2A.75.75%200%200%200%204%201.25zm-.03%207.404a3.5%203.5%200%200%201%201.524-.12.03.03%200%200%201-.012.012L2.415%209.579A.75.75%200%200%200%202%2010.25v1c0%20.414.336.75.75.75h2.5a.75.75%200%200%200%200-1.5H3.927l1.225-.613c.52-.26.848-.79.848-1.371%200-.647-.429-1.327-1.193-1.451a5%205%200%200%200-2.277.155.75.75%200%200%200%20.44%201.434M7.75%203a.75.75%200%200%200%200%201.5h9.5a.75.75%200%200%200%200-1.5zm0%206.25a.75.75%200%200%200%200%201.5h9.5a.75.75%200%200%200%200-1.5zm0%206.25a.75.75%200%200%200%200%201.5h9.5a.75.75%200%200%200%200-1.5zm-5.125-1.625a.75.75%200%200%200%200%201.5h1.5a.125.125%200%200%201%200%20.25H3.5a.75.75%200%200%200%200%201.5h.625a.125.125%200%200%201%200%20.25h-1.5a.75.75%200%200%200%200%201.5h1.5a1.625%201.625%200%200%200%201.37-2.5%201.625%201.625%200%200%200-1.37-2.5z%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20d%3D%22M3%201.25a.75.75%200%200%200%200%201.5h.25v2.5a.75.75%200%200%200%201.5%200V2A.75.75%200%200%200%204%201.25zm-.03%207.404a3.5%203.5%200%200%201%201.524-.12.03.03%200%200%201-.012.012L2.415%209.579A.75.75%200%200%200%202%2010.25v1c0%20.414.336.75.75.75h2.5a.75.75%200%200%200%200-1.5H3.927l1.225-.613c.52-.26.848-.79.848-1.371%200-.647-.429-1.327-1.193-1.451a5%205%200%200%200-2.277.155.75.75%200%200%200%20.44%201.434M7.75%203a.75.75%200%200%200%200%201.5h9.5a.75.75%200%200%200%200-1.5zm0%206.25a.75.75%200%200%200%200%201.5h9.5a.75.75%200%200%200%200-1.5zm0%206.25a.75.75%200%200%200%200%201.5h9.5a.75.75%200%200%200%200-1.5zm-5.125-1.625a.75.75%200%200%200%200%201.5h1.5a.125.125%200%200%201%200%20.25H3.5a.75.75%200%200%200%200%201.5h.625a.125.125%200%200%201%200%20.25h-1.5a.75.75%200%200%200%200%201.5h1.5a1.625%201.625%200%200%200%201.37-2.5%201.625%201.625%200%200%200-1.37-2.5z%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .table:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M.99%205.24A2.25%202.25%200%200%201%203.25%203h13.5A2.25%202.25%200%200%201%2019%205.25l.01%209.5A2.25%202.25%200%200%201%2016.76%2017H3.26A2.267%202.267%200%200%201%201%2014.74zm8.26%209.52v-.625a.75.75%200%200%200-.75-.75H3.25a.75.75%200%200%200-.75.75v.615c0%20.414.336.75.75.75h5.373a.75.75%200%200%200%20.627-.74m1.5%200a.75.75%200%200%200%20.627.74h5.373a.75.75%200%200%200%20.75-.75v-.615a.75.75%200%200%200-.75-.75H11.5a.75.75%200%200%200-.75.75zm6.75-3.63v-.625a.75.75%200%200%200-.75-.75H11.5a.75.75%200%200%200-.75.75v.625c0%20.414.336.75.75.75h5.25a.75.75%200%200%200%20.75-.75m-8.25%200v-.625a.75.75%200%200%200-.75-.75H3.25a.75.75%200%200%200-.75.75v.625c0%20.414.336.75.75.75H8.5a.75.75%200%200%200%20.75-.75M17.5%207.5v-.625a.75.75%200%200%200-.75-.75H11.5a.75.75%200%200%200-.75.75V7.5c0%20.414.336.75.75.75h5.25a.75.75%200%200%200%20.75-.75m-8.25%200v-.625a.75.75%200%200%200-.75-.75H3.25a.75.75%200%200%200-.75.75V7.5c0%20.414.336.75.75.75H8.5a.75.75%200%200%200%20.75-.75%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M.99%205.24A2.25%202.25%200%200%201%203.25%203h13.5A2.25%202.25%200%200%201%2019%205.25l.01%209.5A2.25%202.25%200%200%201%2016.76%2017H3.26A2.267%202.267%200%200%201%201%2014.74zm8.26%209.52v-.625a.75.75%200%200%200-.75-.75H3.25a.75.75%200%200%200-.75.75v.615c0%20.414.336.75.75.75h5.373a.75.75%200%200%200%20.627-.74m1.5%200a.75.75%200%200%200%20.627.74h5.373a.75.75%200%200%200%20.75-.75v-.615a.75.75%200%200%200-.75-.75H11.5a.75.75%200%200%200-.75.75zm6.75-3.63v-.625a.75.75%200%200%200-.75-.75H11.5a.75.75%200%200%200-.75.75v.625c0%20.414.336.75.75.75h5.25a.75.75%200%200%200%20.75-.75m-8.25%200v-.625a.75.75%200%200%200-.75-.75H3.25a.75.75%200%200%200-.75.75v.625c0%20.414.336.75.75.75H8.5a.75.75%200%200%200%20.75-.75M17.5%207.5v-.625a.75.75%200%200%200-.75-.75H11.5a.75.75%200%200%200-.75.75V7.5c0%20.414.336.75.75.75h5.25a.75.75%200%200%200%20.75-.75m-8.25%200v-.625a.75.75%200%200%200-.75-.75H3.25a.75.75%200%200%200-.75.75V7.5c0%20.414.336.75.75.75H8.5a.75.75%200%200%200%20.75-.75%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .upload-image:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M1%205.25A2.25%202.25%200%200%201%203.25%203h13.5A2.25%202.25%200%200%201%2019%205.25v9.5A2.25%202.25%200%200%201%2016.75%2017H3.25A2.25%202.25%200%200%201%201%2014.75zm1.5%205.81v3.69c0%20.414.336.75.75.75h13.5a.75.75%200%200%200%20.75-.75v-2.69l-2.22-2.219a.75.75%200%200%200-1.06%200l-1.91%201.909.47.47a.75.75%200%201%201-1.06%201.06L6.53%208.091a.75.75%200%200%200-1.06%200zM12%207a1%201%200%201%201-2%200%201%201%200%200%201%202%200%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M1%205.25A2.25%202.25%200%200%201%203.25%203h13.5A2.25%202.25%200%200%201%2019%205.25v9.5A2.25%202.25%200%200%201%2016.75%2017H3.25A2.25%202.25%200%200%201%201%2014.75zm1.5%205.81v3.69c0%20.414.336.75.75.75h13.5a.75.75%200%200%200%20.75-.75v-2.69l-2.22-2.219a.75.75%200%200%200-1.06%200l-1.91%201.909.47.47a.75.75%200%201%201-1.06%201.06L6.53%208.091a.75.75%200%200%200-1.06%200zM12%207a1%201%200%201%201-2%200%201%201%200%200%201%202%200%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .undo:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M7.793%202.232a.75.75%200%200%201-.025%201.06L3.622%207.25h10.003a5.375%205.375%200%200%201%200%2010.75H10.75a.75.75%200%200%201%200-1.5h2.875a3.875%203.875%200%200%200%200-7.75H3.622l4.146%203.957a.75.75%200%200%201-1.036%201.085l-5.5-5.25a.75.75%200%200%201%200-1.085l5.5-5.25a.75.75%200%200%201%201.06.025Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M7.793%202.232a.75.75%200%200%201-.025%201.06L3.622%207.25h10.003a5.375%205.375%200%200%201%200%2010.75H10.75a.75.75%200%200%201%200-1.5h2.875a3.875%203.875%200%200%200%200-7.75H3.622l4.146%203.957a.75.75%200%200%201-1.036%201.085l-5.5-5.25a.75.75%200%200%201%200-1.085l5.5-5.25a.75.75%200%200%201%201.06.025Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-toolbar .redo:before{-webkit-mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M12.207%202.232a.75.75%200%200%200%20.025%201.06l4.146%203.958H6.375a5.375%205.375%200%200%200%200%2010.75H9.25a.75.75%200%200%200%200-1.5H6.375a3.875%203.875%200%200%201%200-7.75h10.003l-4.146%203.957a.75.75%200%200%200%201.036%201.085l5.5-5.25a.75.75%200%200%200%200-1.085l-5.5-5.25a.75.75%200%200%200-1.06.025Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E");mask-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22currentColor%22%20class%3D%22size-5%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M12.207%202.232a.75.75%200%200%200%20.025%201.06l4.146%203.958H6.375a5.375%205.375%200%200%200%200%2010.75H9.25a.75.75%200%200%200%200-1.5H6.375a3.875%203.875%200%200%201%200-7.75h10.003l-4.146%203.957a.75.75%200%200%200%201.036%201.085l5.5-5.25a.75.75%200%200%200%200-1.085l-5.5-5.25a.75.75%200%200%200-1.06.025Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E")}.custom-fields-component .fi-fo-markdown-editor .EasyMDEContainer .editor-statusbar{display:none}.custom-fields-component .fi-fo-markdown-editor:where(.dark,.dark *){--color-cm-red:#f87171;--color-cm-orange:#fb923c;--color-cm-amber:#fbbf24;--color-cm-yellow:#facc15;--color-cm-lime:#a3e635;--color-cm-green:#4ade80;--color-cm-emerald:#4ade80;--color-cm-teal:#2dd4bf;--color-cm-cyan:#22d3ee;--color-cm-sky:#38bdf8;--color-cm-blue:#60a5fa;--color-cm-indigo:#818cf8;--color-cm-violet:#a78bfa;--color-cm-purple:#c084fc;--color-cm-fuchsia:#e879f9;--color-cm-pink:#f472b6;--color-cm-rose:#fb7185;--color-cm-gray:#fafafa;--color-cm-gray-muted:#a1a1aa;--color-cm-gray-background:#52525b}.custom-fields-component .fi-fo-markdown-editor:where(.dark,.dark *) .EasyMDEContainer .cm-s-easymde span.CodeMirror-selectedtext{filter:invert()}.custom-fields-component .fi-fo-markdown-editor:where(.dark,.dark *) .EasyMDEContainer .editor-toolbar button:before{background-color:var(--gray-300)}.custom-fields-component .fi-fo-markdown-editor:where(.dark,.dark *) .EasyMDEContainer .editor-toolbar button.active:before{background-color:var(--primary-400)}.custom-fields-component [x-sortable]:has(.fi-sortable-ghost) .fi-fo-markdown-editor{pointer-events:none}.custom-fields-component .fi-fo-modal-table-select:not(.fi-fo-modal-table-select-multiple){align-items:flex-start;column-gap:calc(var(--spacing)*3);--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5);display:flex}.custom-fields-component .fi-fo-modal-table-select.fi-fo-modal-table-select-multiple{gap:calc(var(--spacing)*2);display:grid}.custom-fields-component .fi-fo-modal-table-select.fi-fo-modal-table-select-multiple .fi-fo-modal-table-select-badges-ctn{gap:calc(var(--spacing)*1.5);flex-wrap:wrap;display:flex}.custom-fields-component .fi-fo-modal-table-select .fi-fo-modal-table-select-placeholder{color:var(--gray-400)}.custom-fields-component .fi-fo-modal-table-select .fi-fo-modal-table-select-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-fo-radio{gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-radio.fi-inline{flex-wrap:wrap;display:flex}.custom-fields-component .fi-fo-radio:not(.fi-inline).fi-grid-direction-col{margin-top:calc(var(--spacing)*-4)}.custom-fields-component .fi-fo-radio:not(.fi-inline).fi-grid-direction-col>.fi-fo-radio-label{break-inside:avoid;padding-top:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label{column-gap:calc(var(--spacing)*3);align-self:flex-start;display:flex}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label>.fi-radio-input{margin-top:var(--spacing);flex-shrink:0}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label>.fi-fo-radio-label-text{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950);display:grid}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label>.fi-fo-radio-label-text:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label .fi-fo-radio-label-description{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);color:var(--gray-500)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label .fi-fo-radio-label-description:where(.dark,.dark *),.custom-fields-component .fi-fo-radio>.fi-fo-radio-label:has(.fi-radio-input:disabled)>.fi-fo-radio-label-text{color:var(--gray-400)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label:has(.fi-radio-input:disabled)>.fi-fo-radio-label-text:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label:has(.fi-radio-input:disabled) .fi-fo-radio-label-description{color:var(--gray-300)}.custom-fields-component .fi-fo-radio>.fi-fo-radio-label:has(.fi-radio-input:disabled) .fi-fo-radio-label-description:where(.dark,.dark *){color:var(--gray-600)}.custom-fields-component .fi-fo-repeater{row-gap:calc(var(--spacing)*4);display:grid}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-actions{column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-actions.fi-hidden{display:none}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-items{align-items:flex-start;gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item{border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item.fi-collapsed .fi-fo-repeater-item-header-collapsible-actions{rotate:-180deg}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item.fi-collapsed .fi-fo-repeater-item-header-collapse-action,.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item:not(.fi-collapsed) .fi-fo-repeater-item-header-expand-action{pointer-events:none;opacity:0}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header{align-items:center;column-gap:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);display:flex;overflow:hidden}.custom-fields-component .fi-fo-repeater.fi-collapsible .fi-fo-repeater-item-header{cursor:pointer;-webkit-user-select:none;user-select:none}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-start-actions{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-label.fi-truncated{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-end-actions{align-items:center;column-gap:calc(var(--spacing)*3);margin-inline-start:auto;display:flex}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-collapsible-actions{position:relative}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-collapse-action,.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-collapsible-actions,.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-expand-action{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-header-expand-action{position:absolute;inset:0;rotate:180deg}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-has-header>.fi-fo-repeater-item-content{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-100)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-has-header>.fi-fo-repeater-item-content:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-has-header>.fi-fo-repeater-item-content:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-item-content{padding:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add-between-items-ctn{justify-content:center;width:100%;display:flex}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add-between-items{border-radius:var(--radius-lg);background-color:var(--color-white)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add-between-items:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-ctn{margin-block:calc(var(--spacing)*-2);align-items:center;display:flex;position:relative}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-before{width:calc(var(--spacing)*3);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-200);flex-shrink:0}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-before:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-before:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items{padding-inline:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-500);flex-shrink:0}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-after{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-200);flex:1}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-after:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-repeater .fi-fo-repeater-label-between-items-divider-after:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add{justify-content:center;width:100%;display:flex}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add.fi-align-left,.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add.fi-align-end,.custom-fields-component .fi-fo-repeater .fi-fo-repeater-add.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-fo-simple-repeater{row-gap:calc(var(--spacing)*4);display:grid}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-items{gap:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-item{justify-content:flex-start;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-item-content{flex:1}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-item-actions{align-items:center;column-gap:var(--spacing);display:flex}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-add{justify-content:center;width:100%;display:flex}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-add.fi-align-left,.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-add.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-add.fi-align-end,.custom-fields-component .fi-fo-simple-repeater .fi-fo-simple-repeater-add.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-fo-table-repeater{gap:calc(var(--spacing)*3);display:grid}.custom-fields-component .fi-fo-table-repeater>table{width:100%;display:block}.custom-fields-component :where(.fi-fo-table-repeater>table>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component .fi-fo-table-repeater>table{border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater>table{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component :where(.fi-fo-table-repeater>table:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-table-repeater>table:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-table-repeater>table:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater>table:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-fo-table-repeater>table>thead{white-space:nowrap;display:none}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th{border-color:var(--gray-200);background-color:var(--gray-50);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:first-of-type{border-start-start-radius:var(--radius-xl)}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:last-of-type{border-start-end-radius:var(--radius-xl)}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-align-left,.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-align-start{text-align:start}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-align-end,.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-align-right{text-align:end}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-wrapped{white-space:normal}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th:not(.fi-wrapped){white-space:nowrap}.custom-fields-component .fi-fo-table-repeater>table>thead>tr>th.fi-fo-table-repeater-empty-header-cell{width:var(--spacing)}.custom-fields-component .fi-fo-table-repeater>table>tbody{display:block}.custom-fields-component :where(.fi-fo-table-repeater>table>tbody>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-fo-table-repeater>table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-table-repeater>table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr{gap:calc(var(--spacing)*6);padding:calc(var(--spacing)*6);display:grid}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td{display:block}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-hidden{display:none}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-vertical-align-start{vertical-align:top}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-vertical-align-center{vertical-align:middle}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-vertical-align-end{vertical-align:bottom}.custom-fields-component .fi-fo-table-repeater>table .fi-fo-table-repeater-header-required-mark{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--danger-600)}.custom-fields-component .fi-fo-table-repeater>table .fi-fo-table-repeater-header-required-mark:where(.dark,.dark *){color:var(--danger-400)}.custom-fields-component .fi-fo-table-repeater>table .fi-fo-table-repeater-actions{align-items:center;column-gap:calc(var(--spacing)*3);height:100%;display:flex}@supports (container-type:inline-size){.custom-fields-component .fi-fo-table-repeater{container-type:inline-size}@container (min-width:36rem){.custom-fields-component .fi-fo-table-repeater>table{display:table}.custom-fields-component .fi-fo-table-repeater>table>thead{display:table-header-group}.custom-fields-component .fi-fo-table-repeater>table>tbody{display:table-row-group}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr{padding:0;display:table-row}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);display:table-cell}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-hidden{display:table-cell}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-fo-field,.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-in-entry{row-gap:0}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-fo-field-label-content,.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-in-entry-label{display:none}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-fo-toggle-buttons-wrp .fi-fo-field-content-col{grid-auto-columns:1fr}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td{border-color:var(--gray-200);padding-inline:0;padding-block:var(--spacing)}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-input-wrp{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)!important;background-color:#0000!important}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-fo-table-repeater-actions{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-fo-field-wrp-error-message{padding-inline:calc(var(--spacing)*3);padding-bottom:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-fo-radio,.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-in-entry-content{padding-inline:calc(var(--spacing)*3)}}}@supports not (container-type:inline-size){@media (min-width:64rem){.custom-fields-component .fi-fo-table-repeater>table{display:table}.custom-fields-component .fi-fo-table-repeater>table>thead{display:table-header-group}.custom-fields-component .fi-fo-table-repeater>table>tbody{display:table-row-group}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr{padding:0;display:table-row}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);display:table-cell}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td.fi-hidden{display:table-cell}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-fo-field,.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-in-entry{row-gap:0}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-fo-field-label-content,.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-in-entry-label{display:none}.custom-fields-component .fi-fo-table-repeater>table>tbody>tr>td .fi-fo-toggle-buttons-wrp .fi-fo-field-content-col{grid-auto-columns:1fr}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td{border-color:var(--gray-200);padding-inline:0;padding-block:var(--spacing)}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-table-repeater.fi-compact>table>tbody>tr>td:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-input-wrp{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)!important;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)!important;background-color:#0000!important}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-fo-table-repeater-actions{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-fo-field-wrp-error-message{padding-inline:calc(var(--spacing)*3);padding-bottom:calc(var(--spacing)*2)}.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-fo-radio,.custom-fields-component .fi-fo-table-repeater.fi-compact .fi-in-entry-content{padding-inline:calc(var(--spacing)*3)}}}.custom-fields-component .fi-fo-table-repeater .fi-fo-table-repeater-add{justify-content:center;width:100%;display:flex}.custom-fields-component .fi-fo-table-repeater .fi-fo-table-repeater-add.fi-align-left,.custom-fields-component .fi-fo-table-repeater .fi-fo-table-repeater-add.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-fo-table-repeater .fi-fo-table-repeater-add.fi-align-end,.custom-fields-component .fi-fo-table-repeater .fi-fo-table-repeater-add.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file{pointer-events:none;cursor:wait;opacity:.5}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-toolbar{column-gap:calc(var(--spacing)*3);row-gap:var(--spacing);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);padding-inline:calc(var(--spacing)*2.5);padding-block:calc(var(--spacing)*2);flex-wrap:wrap;display:flex;position:relative}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-toolbar:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-toolbar:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-floating-toolbar{visibility:hidden;z-index:20;margin-top:calc(var(--spacing)*-1);column-gap:calc(var(--spacing)*3);row-gap:var(--spacing);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--gray-300);background-color:var(--color-white);max-width:100%;padding:var(--spacing);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);flex-wrap:wrap;display:flex;position:absolute}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-floating-toolbar:where(.dark,.dark *){border-color:var(--gray-600);background-color:var(--gray-800)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-toolbar-group{column-gap:var(--spacing);display:flex}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool{display:inline-flex;position:relative}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger{height:calc(var(--spacing)*8);cursor:pointer;justify-content:center;align-items:center;gap:calc(var(--spacing)*.5);border-radius:var(--radius-lg);--tw-border-style:none;padding-inline:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-700);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;--tw-outline-style:none;background-color:#0000;border-style:none;outline-style:none;transition-duration:75ms;display:flex}@media (hover:hover){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger:focus-visible{background-color:var(--gray-50)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger:where(.dark,.dark *){color:var(--gray-200)}@media (hover:hover){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger.fi-active{background-color:var(--gray-50);color:var(--primary-600)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger.fi-active:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger.fi-active:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-trigger.fi-active:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-chevron{height:calc(var(--spacing)*3);width:calc(var(--spacing)*3);color:var(--gray-400)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-chevron:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-menu{z-index:30;gap:var(--spacing);border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--gray-300);background-color:var(--color-white);padding:var(--spacing);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);inset-inline-start:calc(var(--spacing)*0);display:flex;position:absolute;top:calc(100% + .25rem)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-menu:where(.dark,.dark *){border-color:var(--gray-600);background-color:var(--gray-800)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option{height:calc(var(--spacing)*8);min-width:calc(var(--spacing)*8);cursor:pointer;border-radius:var(--radius-lg);--tw-border-style:none;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-700);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;--tw-outline-style:none;background-color:#0000;border-style:none;outline-style:none;justify-content:center;align-items:center;padding:0;transition-duration:75ms;display:flex}@media (hover:hover){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option:focus-visible{background-color:var(--gray-50)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option:where(.dark,.dark *){color:var(--gray-200)}@media (hover:hover){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option.fi-active{background-color:var(--gray-50);color:var(--primary-600)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option.fi-active:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option.fi-active:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-option.fi-active:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-textual .fi-fo-rich-editor-dropdown-tool-menu{min-width:calc(var(--spacing)*32);flex-direction:column}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-dropdown-tool-textual .fi-fo-rich-editor-dropdown-tool-option{justify-content:flex-start;gap:calc(var(--spacing)*2);min-width:0;padding-inline:calc(var(--spacing)*2);padding-block:var(--spacing);--tw-font-weight:var(--font-weight-normal);font-size:.8125rem;font-weight:var(--font-weight-normal);white-space:nowrap}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool{height:calc(var(--spacing)*8);min-width:calc(var(--spacing)*8);border-radius:var(--radius-lg);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-700);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;justify-content:center;align-items:center;transition-duration:75ms;display:flex}@media (hover:hover){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:focus-visible{background-color:var(--gray-50)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:where(.dark,.dark *){color:var(--gray-200)}@media (hover:hover){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool[disabled]{pointer-events:none;cursor:default;opacity:.7}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool.fi-active{background-color:var(--gray-50);color:var(--primary-600)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool.fi-active:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool.fi-active:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool.fi-active:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-tool.fi-fo-rich-editor-tool-with-label{align-items:center;column-gap:var(--spacing);padding-inline:calc(var(--spacing)*1.5)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message{align-items:center;column-gap:calc(var(--spacing)*3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);background-color:var(--gray-50);padding-inline:calc(var(--spacing)*5);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-700);display:flex}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message .fi-loading-indicator{color:var(--gray-400)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-uploading-file-message .fi-loading-indicator:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message{align-items:center;column-gap:calc(var(--spacing)*3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--danger-200);background-color:var(--danger-50);padding-inline:calc(var(--spacing)*5);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--danger-700);display:flex}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-file-validation-message:where(.dark,.dark *){color:var(--danger-200)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-main{flex-direction:column-reverse;display:flex}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-content{min-height:calc(var(--spacing)*12);width:100%;padding-inline:calc(var(--spacing)*5);padding-block:calc(var(--spacing)*3);flex:1;position:relative}.custom-fields-component .fi-fo-rich-editor span[data-type=mergeTag]{white-space:nowrap;margin-block:0;display:inline-block}.custom-fields-component .fi-fo-rich-editor span[data-type=mergeTag]:before{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);opacity:.6;content:"{{";margin-inline-end:var(--spacing)}.custom-fields-component .fi-fo-rich-editor span[data-type=mergeTag]:after{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);opacity:.6;content:"}}";margin-inline-start:var(--spacing)}.custom-fields-component .fi-fo-rich-editor span[data-type=mention]{background-color:var(--primary-50);padding-inline:var(--spacing);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--primary-600);border-radius:.25rem;margin-block:0;display:inline-block}.custom-fields-component .fi-fo-rich-editor span[data-type=mention]:where(.dark,.dark *){background-color:var(--primary-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor span[data-type=mention]:where(.dark,.dark *){background-color:color-mix(in oklab,var(--primary-400) 10%,transparent)}}.custom-fields-component .fi-fo-rich-editor span[data-type=mention]:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);background-color:var(--gray-50);width:100%}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900) 30%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panel-header{align-items:flex-start;gap:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panel-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950);flex:1}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panel-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panel-close-btn-ctn{flex-shrink:0}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panel{display:grid}.custom-fields-component :where(.fi-fo-rich-editor .fi-fo-rich-editor-panel>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-fo-rich-editor .fi-fo-rich-editor-panel:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-rich-editor .fi-fo-rich-editor-panel:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tags-list{gap:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);flex-wrap:wrap;display:flex}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn{cursor:move;border-radius:var(--radius-lg);background-color:var(--color-white);padding:var(--spacing);text-align:start;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-600);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn{--tw-ring-color:color-mix(in oklab, var(--gray-600) 10%, transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn:where(.dark,.dark *){background-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-400) 10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn:where(.dark,.dark *){color:var(--gray-200);--tw-ring-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-merge-tag-btn:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--gray-400) 20%, transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-blocks-ctn{display:grid;overflow-y:auto}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-blocks-group-header{z-index:10;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);background-color:var(--gray-50);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950);text-transform:capitalize;position:sticky;top:-1px}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-blocks-group-header:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-blocks-group-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-blocks-group-header:where(.dark,.dark *){background-color:var(--gray-800);color:var(--color-white)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-blocks-group-header:not(:first-child){border-top-style:var(--tw-border-style);border-top-width:1px}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-blocks-list{gap:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);flex-wrap:wrap;display:flex}.custom-fields-component .fi-fo-rich-editor:has(.fi-fo-rich-editor-custom-blocks-group-header) .fi-fo-rich-editor-custom-blocks-list{background-color:var(--color-white)}.custom-fields-component .fi-fo-rich-editor:has(.fi-fo-rich-editor-custom-blocks-group-header) .fi-fo-rich-editor-custom-blocks-list:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn{cursor:move;gap:calc(var(--spacing)*1.5);border-radius:var(--radius-lg);background-color:var(--color-white);padding-inline:calc(var(--spacing)*2);padding-block:var(--spacing);text-align:start;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-600);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-600);display:flex}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn{--tw-ring-color:color-mix(in oklab, var(--gray-600) 10%, transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn:where(.dark,.dark *){background-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-400) 10%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn:where(.dark,.dark *){color:var(--gray-200);--tw-ring-color:var(--gray-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-btn:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--gray-400) 20%, transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap{height:100%}.custom-fields-component .fi-fo-rich-editor .tiptap:focus{--tw-outline-style:none;outline-style:none}.custom-fields-component div:is(.fi-fo-rich-editor .tiptap:focus .ProseMirror-selectednode)[data-type=customBlock],.custom-fields-component img:is(.fi-fo-rich-editor .tiptap:focus .ProseMirror-selectednode){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component :is(div:is(.fi-fo-rich-editor .tiptap:focus .ProseMirror-selectednode)[data-type=customBlock],img:is(.fi-fo-rich-editor .tiptap:focus .ProseMirror-selectednode)):where(.dark,.dark *){--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-fo-rich-editor .tiptap p.is-editor-empty:first-child:before{pointer-events:none;float:inline-start;height:0;color:var(--gray-400);content:attr(data-placeholder)}.custom-fields-component .fi-fo-rich-editor .tiptap p.is-editor-empty:first-child:where(.dark,.dark *):before{color:var(--gray-500)}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]{margin-block:calc(var(--spacing)*6);gap:var(--spacing);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--gray-950);display:flex}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]{border-color:color-mix(in oklab,var(--gray-950) 20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]{padding:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]:where(.dark,.dark *){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>div:first-of-type{margin-top:0!important}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details] summary{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);list-style-type:none}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button{margin-top:1px;margin-right:calc(var(--spacing)*2);width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);border-radius:var(--radius-md);padding:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1;background-color:#0000;justify-content:center;align-items:center;line-height:1;display:flex}@media (hover:hover){.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button:hover{background-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button:hover{background-color:color-mix(in oklab,var(--gray-950) 5%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>button:before{content:"ā–¶"}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details].is-open>button:before{transform:rotate(90deg)}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>div{gap:calc(var(--spacing)*4);flex-direction:column;width:100%;display:flex}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>div>[data-type=detailsContent]{margin-top:0!important}.custom-fields-component .fi-fo-rich-editor .tiptap [data-type=details]>div>[data-type=detailsContent]>:last-child{margin-bottom:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-rich-editor .tiptap table{table-layout:fixed;border-collapse:collapse;width:100%;margin:0;overflow:hidden}.custom-fields-component .fi-fo-rich-editor .tiptap table:first-child{margin-top:0}.custom-fields-component .fi-fo-rich-editor .tiptap table td,.custom-fields-component .fi-fo-rich-editor .tiptap table th{border-style:var(--tw-border-style);border-width:1px;border-color:var(--gray-300);vertical-align:top;min-width:1em;position:relative;padding:calc(var(--spacing)*2)!important}.custom-fields-component :is(.fi-fo-rich-editor .tiptap table td,.fi-fo-rich-editor .tiptap table th):where(.dark,.dark *){border-color:var(--gray-600)}.custom-fields-component :is(.fi-fo-rich-editor .tiptap table td,.fi-fo-rich-editor .tiptap table th)>*{margin-bottom:0}.custom-fields-component .fi-fo-rich-editor .tiptap table th{background-color:var(--gray-100);text-align:start;--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.custom-fields-component .fi-fo-rich-editor .tiptap table th:where(.dark,.dark *){background-color:var(--gray-800);color:var(--color-white)}.custom-fields-component .fi-fo-rich-editor .tiptap table .selectedCell:after{pointer-events:none;z-index:2;background-color:var(--gray-200);inset-inline-start:calc(var(--spacing)*0);inset-inline-end:calc(var(--spacing)*0);position:absolute;top:0;bottom:0}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap table .selectedCell:after{background-color:color-mix(in oklab,var(--gray-200) 80%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap table .selectedCell:after{--tw-content:"";content:var(--tw-content)}.custom-fields-component .fi-fo-rich-editor .tiptap table .selectedCell:where(.dark,.dark *):after{background-color:var(--gray-800)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap table .selectedCell:where(.dark,.dark *):after{background-color:color-mix(in oklab,var(--gray-800) 80%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap table .column-resize-handle{pointer-events:none;width:var(--spacing);background-color:var(--primary-600);inset-inline-end:calc(var(--spacing)*0);position:absolute;top:0;bottom:0;margin:0!important}.custom-fields-component .fi-fo-rich-editor .tiptap .tableWrapper{overflow-x:auto}.custom-fields-component .fi-fo-rich-editor .tiptap.resize-cursor{cursor:col-resize;cursor:ew-resize}.custom-fields-component .fi-fo-rich-editor .tiptap .grid-layout>.grid-layout-col{border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap .grid-layout>.grid-layout-col{border-color:color-mix(in oklab,var(--gray-950) 20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap .grid-layout>.grid-layout-col{padding:calc(var(--spacing)*4)}.custom-fields-component .fi-fo-rich-editor .tiptap .grid-layout>.grid-layout-col:where(.dark,.dark *){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .tiptap .grid-layout>.grid-layout-col:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle]{z-index:10;background:#00000080;border:1px solid #fffc;border-radius:2px;position:absolute}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle]:hover{background:#000c}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle]{margin:0!important}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom-left],.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom-right],.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top-left],.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top-right]{width:8px;height:8px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top-left]{cursor:nwse-resize;top:-4px;left:-4px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top-right]{cursor:nesw-resize;top:-4px;right:-4px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom-left]{cursor:nesw-resize;bottom:-4px;left:-4px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom-right]{cursor:nwse-resize;bottom:-4px;right:-4px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom],.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top]{height:6px;left:8px;right:8px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=top]{cursor:ns-resize;top:-3px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=bottom]{cursor:ns-resize;bottom:-3px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=left],.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=right]{width:6px;top:8px;bottom:8px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=left]{cursor:ew-resize;left:-3px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-handle][data-resize-handle=right]{cursor:ew-resize;right:-3px}.custom-fields-component .fi-fo-rich-editor .tiptap [data-resize-state=true] [data-resize-wrapper]{border-radius:.125rem;outline:1px solid #00000040;position:relative}.custom-fields-component .fi-fo-rich-editor.fi-disabled [data-resize-handle]{display:none}.custom-fields-component .fi-fo-rich-editor.fi-disabled [data-resize-state=true] [data-resize-wrapper]{outline:none}@supports (-webkit-touch-callout:none){.custom-fields-component .fi-fo-rich-editor .tiptap.ProseMirror{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.custom-fields-component .fi-fo-rich-editor img{display:inline-block}.custom-fields-component .fi-fo-rich-editor div[data-type=customBlock]{display:grid}.custom-fields-component :where(.fi-fo-rich-editor div[data-type=customBlock]>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component .fi-fo-rich-editor div[data-type=customBlock]{border-radius:var(--radius-lg);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);overflow:hidden}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor div[data-type=customBlock]{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component :where(.fi-fo-rich-editor div[data-type=customBlock]:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-fo-rich-editor div[data-type=customBlock]:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-rich-editor div[data-type=customBlock]:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor div[data-type=customBlock]:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-header{align-items:flex-start;gap:calc(var(--spacing)*3);background-color:var(--gray-50);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-header:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-header:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900) 30%,transparent)}}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950);flex:1}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-delete-btn-ctn,.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-edit-btn-ctn{flex-shrink:0}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-custom-block-preview{padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3)}@supports (container-type:inline-size){.custom-fields-component .fi-fo-rich-editor{container-type:inline-size}@container (min-width:42rem){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-main{flex-direction:row}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels{max-width:var(--container-3xs);border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px;border-bottom-style:var(--tw-border-style);border-bottom-width:0;border-end-end-radius:var(--radius-lg)}}}@supports not (container-type:inline-size){@media (min-width:48rem){.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-main{flex-direction:row}.custom-fields-component .fi-fo-rich-editor .fi-fo-rich-editor-panels{max-width:var(--container-3xs);border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px;border-bottom-style:var(--tw-border-style);border-bottom-width:0;border-end-end-radius:var(--radius-lg)}}}.custom-fields-component :scope .fi-fo-rich-editor-text-color-select-option{align-items:center;gap:calc(var(--spacing)*2);display:flex}.custom-fields-component :scope .fi-fo-rich-editor-text-color-select-option .fi-fo-rich-editor-text-color-select-option-preview{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5);background-color:var(--color);border-radius:3.40282e+38px;flex-shrink:0}.custom-fields-component :scope .fi-fo-rich-editor-text-color-select-option .fi-fo-rich-editor-text-color-select-option-preview:where(.dark,.dark *){background-color:var(--dark-color)}.custom-fields-component [x-sortable]:has(.fi-sortable-ghost) .fi-fo-rich-editor{pointer-events:none}.custom-fields-component .fi-fo-select .fi-hidden{display:none}@media (min-width:40rem){.custom-fields-component .fi-fo-select-wrp.fi-fo-field-has-inline-label .fi-fo-field-label-col{padding-top:calc(var(--spacing)*1.5)}}.custom-fields-component .fi-fo-slider{gap:calc(var(--spacing)*4);border-radius:var(--radius-lg);border-style:var(--tw-border-style);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);background-color:#0000;border-width:0}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-fo-slider:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-fo-slider .noUi-connect{background-color:var(--primary-500)}.custom-fields-component .fi-fo-slider .noUi-connect:where(.dark,.dark *){background-color:var(--primary-600)}.custom-fields-component .fi-fo-slider .noUi-connects{border-radius:var(--radius-lg);background-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-connects{background-color:color-mix(in oklab,var(--gray-950) 5%,transparent)}}.custom-fields-component .fi-fo-slider .noUi-connects:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-connects:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-fo-slider .noUi-handle{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--gray-950);position:absolute}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-handle{border-color:color-mix(in oklab,var(--gray-950) 10%,transparent)}}.custom-fields-component .fi-fo-slider .noUi-handle{background-color:var(--color-white);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);backface-visibility:hidden}.custom-fields-component .fi-fo-slider .noUi-handle:focus{outline-style:var(--tw-outline-style);outline-width:2px;outline-color:var(--primary-600)}.custom-fields-component .fi-fo-slider .noUi-handle:where(.dark,.dark *){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-handle:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.custom-fields-component .fi-fo-slider .noUi-handle:where(.dark,.dark *){background-color:var(--gray-700)}.custom-fields-component .fi-fo-slider .noUi-handle:where(.dark,.dark *):focus{outline-color:var(--primary-500)}.custom-fields-component .fi-fo-slider .noUi-handle:after,.custom-fields-component .fi-fo-slider .noUi-handle:before{border-style:var(--tw-border-style);background-color:var(--gray-400);border-width:0}.custom-fields-component .fi-fo-slider .noUi-tooltip{border-radius:var(--radius-md);border-style:var(--tw-border-style);background-color:var(--color-white);color:var(--gray-950);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);border-width:0}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-tooltip{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-fo-slider .noUi-tooltip:where(.dark,.dark *){background-color:var(--gray-800);color:var(--color-white);--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-slider .noUi-tooltip:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-fo-slider .noUi-pips .noUi-value{color:var(--gray-950)}.custom-fields-component .fi-fo-slider .noUi-pips .noUi-value:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-slider.fi-fo-slider-vertical{margin-top:calc(var(--spacing)*4);height:calc(var(--spacing)*40)}.custom-fields-component .fi-fo-slider.fi-fo-slider-vertical.fi-fo-slider-has-tooltips{margin-inline-start:calc(var(--spacing)*10)}.custom-fields-component .fi-fo-slider:not(.fi-fo-slider-vertical).fi-fo-slider-has-pips{margin-bottom:calc(var(--spacing)*8)}.custom-fields-component .fi-fo-slider:not(.fi-fo-slider-vertical).fi-fo-slider-has-tooltips{margin-top:calc(var(--spacing)*10)}.custom-fields-component .fi-fo-slider:not(.fi-fo-slider-vertical) .noUi-pips .noUi-value{margin-top:var(--spacing)}.custom-fields-component .fi-fo-tags-input.fi-disabled .fi-badge-delete-btn{display:none}.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn{gap:calc(var(--spacing)*1.5);border-top-style:var(--tw-border-style);border-top-width:1px;border-top-color:var(--gray-200);width:100%;padding:calc(var(--spacing)*2);flex-wrap:wrap;display:flex}.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn:where(.dark,.dark *){border-top-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn:where(.dark,.dark *){border-top-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn>template{display:none}.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn>.fi-badge.fi-reorderable{cursor:move}.custom-fields-component .fi-fo-tags-input .fi-fo-tags-input-tags-ctn>.fi-badge .fi-badge-label-ctn{text-align:start;-webkit-user-select:none;user-select:none}@media (min-width:40rem){.custom-fields-component .fi-fo-tags-input-wrp.fi-fo-field-has-inline-label .fi-fo-field-label-col{padding-top:calc(var(--spacing)*1.5)}}.custom-fields-component .fi-fo-text-input{overflow:hidden}.custom-fields-component .fi-fo-text-input input.fi-revealable::-ms-reveal{display:none}.custom-fields-component .fi-fo-textarea{overflow:hidden}.custom-fields-component .fi-fo-textarea textarea{--tw-border-style:none;width:100%;height:100%;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);color:var(--gray-950);background-color:#0000;border-style:none;display:block}.custom-fields-component .fi-fo-textarea textarea::placeholder{color:var(--gray-400)}.custom-fields-component .fi-fo-textarea textarea:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-fo-textarea textarea:disabled{color:var(--gray-500);-webkit-text-fill-color:var(--color-gray-500)}.custom-fields-component .fi-fo-textarea textarea:disabled::placeholder{-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component .fi-fo-textarea textarea:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-fo-textarea textarea:where(.dark,.dark *)::placeholder{color:var(--gray-500)}.custom-fields-component .fi-fo-textarea textarea:where(.dark,.dark *):disabled{color:var(--gray-400);-webkit-text-fill-color:var(--color-gray-400)}.custom-fields-component .fi-fo-textarea textarea:where(.dark,.dark *):disabled::placeholder{-webkit-text-fill-color:var(--color-gray-500)}@supports (-webkit-touch-callout:none){.custom-fields-component .fi-fo-textarea textarea{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.custom-fields-component .fi-fo-textarea.fi-autosizable textarea{resize:none}@media (min-width:40rem){.custom-fields-component .fi-fo-textarea-wrp.fi-fo-field-has-inline-label .fi-fo-field-label-col{padding-top:calc(var(--spacing)*1.5)}}.custom-fields-component .fi-fo-toggle-buttons.fi-btn-group{width:max-content}.custom-fields-component .fi-fo-toggle-buttons:not(.fi-btn-group){gap:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-toggle-buttons:not(.fi-btn-group).fi-inline{flex-wrap:wrap;display:flex}.custom-fields-component .fi-fo-toggle-buttons:not(.fi-btn-group):not(.fi-inline).fi-grid-direction-col{margin-top:calc(var(--spacing)*-3)}.custom-fields-component .fi-fo-toggle-buttons:not(.fi-btn-group):not(.fi-inline).fi-grid-direction-col .fi-fo-toggle-buttons-btn-ctn{break-inside:avoid;padding-top:calc(var(--spacing)*3)}.custom-fields-component .fi-fo-toggle-buttons .fi-fo-toggle-buttons-input{pointer-events:none;opacity:0;position:absolute}@media (min-width:40rem){.custom-fields-component .fi-fo-toggle-buttons-wrp.fi-fo-field-has-inline-label .fi-fo-field-label-col{padding-top:calc(var(--spacing)*1.5)}}.custom-fields-component .fi-in-code .phiki{border-radius:var(--radius-lg);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);overflow-x:auto}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-code .phiki{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-in-code .phiki:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-code .phiki:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-in-code:where(.dark,.dark *) .phiki,.custom-fields-component .fi-in-code:where(.dark,.dark *) .phiki span{color:var(--phiki-dark-color)!important;background-color:var(--phiki-dark-background-color)!important;font-style:var(--phiki-dark-font-style)!important;font-weight:var(--phiki-dark-font-weight)!important;-webkit-text-decoration:var(--phiki-dark-text-decoration)!important;text-decoration:var(--phiki-dark-text-decoration)!important}.custom-fields-component .fi-in-code.fi-copyable{cursor:pointer}.custom-fields-component .fi-in-color{gap:calc(var(--spacing)*1.5);width:100%;display:flex}.custom-fields-component .fi-in-color.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-in-color.fi-align-left,.custom-fields-component .fi-in-color.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-in-color.fi-align-center{justify-content:center}.custom-fields-component .fi-in-color.fi-align-end,.custom-fields-component .fi-in-color.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-in-color.fi-align-between,.custom-fields-component .fi-in-color.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-in-color>.fi-in-color-item{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6);border-radius:var(--radius-md);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-color>.fi-in-color-item{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-in-color>.fi-in-color-item:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-color>.fi-in-color-item:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-in-color>.fi-in-color-item.fi-copyable{cursor:pointer;--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-in-color>.fi-in-color-item.fi-copyable:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component .fi-in-color>.fi-in-color-item.fi-copyable:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-in-entry{row-gap:calc(var(--spacing)*2);display:grid}@media (min-width:40rem){.custom-fields-component .fi-in-entry.fi-in-entry-has-inline-label{align-items:flex-start;column-gap:calc(var(--spacing)*4);grid-template-columns:repeat(3,minmax(0,1fr))}.custom-fields-component .fi-in-entry.fi-in-entry-has-inline-label .fi-in-entry-content-col{grid-column:span 2/span 2}}.custom-fields-component .fi-in-entry .fi-in-entry-label-ctn{align-items:flex-start;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-in-entry .fi-in-entry-label-ctn>.fi-sc:first-child{flex-grow:0}.custom-fields-component .fi-in-entry .fi-in-entry-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-in-entry .fi-in-entry-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-in-entry .fi-in-entry-content-col,.custom-fields-component .fi-in-entry .fi-in-entry-label-col{row-gap:calc(var(--spacing)*2);grid-auto-columns:minmax(0,1fr);display:grid}.custom-fields-component .fi-in-entry .fi-in-entry-content-ctn{align-items:center;column-gap:calc(var(--spacing)*3);width:100%;display:flex}.custom-fields-component .fi-in-entry .fi-in-entry-content{text-align:start;width:100%;display:block}.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-center{text-align:center}.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-end{text-align:end}.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-left{text-align:left}.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-right{text-align:right}.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-between,.custom-fields-component .fi-in-entry .fi-in-entry-content.fi-align-justify{text-align:justify}.custom-fields-component .fi-in-entry .fi-in-placeholder{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-400)}.custom-fields-component .fi-in-entry .fi-in-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-in-key-value{table-layout:auto;width:100%}.custom-fields-component :where(.fi-in-key-value>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component .fi-in-key-value{border-radius:var(--radius-lg);background-color:var(--color-white);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-key-value{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component :where(.fi-in-key-value:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-in-key-value:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-in-key-value:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-key-value:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-in-key-value:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-key-value:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-in-key-value thead th{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);text-align:start;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-700)}.custom-fields-component .fi-in-key-value thead th:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component :where(.fi-in-key-value tbody>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component .fi-in-key-value tbody{font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}@media (min-width:40rem){.custom-fields-component .fi-in-key-value tbody{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}}.custom-fields-component :where(.fi-in-key-value tbody:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-in-key-value tbody:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component :where(.fi-in-key-value tr>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-in-key-value tr:where(:dir(rtl),[dir=rtl],[dir=rtl] *)>:not(:last-child)){--tw-divide-x-reverse:1}.custom-fields-component :where(.fi-in-key-value tr:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-in-key-value tr:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-in-key-value tbody td,.custom-fields-component .fi-in-key-value tbody th{width:50%;padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);text-align:start;--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);overflow-wrap:anywhere}.custom-fields-component :is(.fi-in-key-value tbody th,.fi-in-key-value tbody td).fi-in-placeholder{width:100%;padding-block:calc(var(--spacing)*2);text-align:center;font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.custom-fields-component .fi-in-icon{gap:calc(var(--spacing)*1.5);width:100%;display:flex}.custom-fields-component .fi-in-icon.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-in-icon.fi-in-icon-has-line-breaks{flex-direction:column}.custom-fields-component .fi-in-icon.fi-align-left,.custom-fields-component .fi-in-icon.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-in-icon.fi-align-center{justify-content:center}.custom-fields-component .fi-in-icon.fi-align-end,.custom-fields-component .fi-in-icon.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-in-icon.fi-align-between,.custom-fields-component .fi-in-icon.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-in-icon>.fi-icon,.custom-fields-component .fi-in-icon>a>.fi-icon{color:var(--gray-400)}.custom-fields-component :is(.fi-in-icon>.fi-icon,.fi-in-icon>a>.fi-icon):where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component :is(.fi-in-icon>.fi-icon,.fi-in-icon>a>.fi-icon).fi-color{color:var(--text)}.custom-fields-component :is(.fi-in-icon>.fi-icon,.fi-in-icon>a>.fi-icon).fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-in-image{align-items:center;gap:calc(var(--spacing)*1.5);width:100%;display:flex}.custom-fields-component .fi-in-image img{object-fit:cover;object-position:center;max-width:none}.custom-fields-component .fi-in-image.fi-circular img{border-radius:3.40282e+38px}.custom-fields-component .fi-in-image.fi-in-image-ring .fi-in-image-limited-remaining-text,.custom-fields-component .fi-in-image.fi-in-image-ring img{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-white)}.custom-fields-component :is(.fi-in-image.fi-in-image-ring img,.fi-in-image.fi-in-image-ring .fi-in-image-limited-remaining-text):where(.dark,.dark *){--tw-ring-color:var(--gray-900)}.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-1 .fi-in-image-limited-remaining-text,.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-1 img{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-2 .fi-in-image-limited-remaining-text,.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-2 img{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-4 .fi-in-image-limited-remaining-text,.custom-fields-component .fi-in-image.fi-in-image-ring.fi-in-image-ring-4 img{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-in-image.fi-in-image-overlap-1{column-gap:0}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-1*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-1*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-in-image.fi-in-image-overlap-2{column-gap:0}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-2*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-2*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-in-image.fi-in-image-overlap-3{column-gap:0}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-3*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-3*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-in-image.fi-in-image-overlap-4{column-gap:0}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-4*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-4*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-in-image.fi-in-image-overlap-5{column-gap:0}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-5*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-5*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-in-image.fi-in-image-overlap-6{column-gap:0}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-6*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-6*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-in-image.fi-in-image-overlap-7{column-gap:0}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-7>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-7*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-7*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-in-image.fi-in-image-overlap-8{column-gap:0}.custom-fields-component :where(.fi-in-image.fi-in-image-overlap-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-8*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-8*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-in-image.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-in-image.fi-align-left,.custom-fields-component .fi-in-image.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-in-image.fi-align-center{justify-content:center}.custom-fields-component .fi-in-image.fi-align-end,.custom-fields-component .fi-in-image.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-in-image.fi-align-between,.custom-fields-component .fi-in-image.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-in-image.fi-stacked .fi-in-image-limited-remaining-text{background-color:var(--gray-100);border-radius:3.40282e+38px}.custom-fields-component .fi-in-image.fi-stacked .fi-in-image-limited-remaining-text:where(.dark,.dark *){background-color:var(--gray-800)}.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-500);justify-content:center;align-items:center;display:flex}.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text.fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text.fi-size-base,.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text.fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-in-image .fi-in-image-limited-remaining-text.fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component ul.fi-in-repeatable{gap:calc(var(--spacing)*4)}.custom-fields-component .fi-in-repeatable>.fi-in-repeatable-item{display:block}.custom-fields-component .fi-in-repeatable.fi-contained>.fi-in-repeatable-item{border-radius:var(--radius-xl);background-color:var(--color-white);padding:calc(var(--spacing)*4);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-repeatable.fi-contained>.fi-in-repeatable-item{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-in-repeatable.fi-contained>.fi-in-repeatable-item:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-repeatable.fi-contained>.fi-in-repeatable-item:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-in-repeatable.fi-contained>.fi-in-repeatable-item:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-repeatable.fi-contained>.fi-in-repeatable-item:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-in-table-repeatable{gap:calc(var(--spacing)*3);display:grid}.custom-fields-component .fi-in-table-repeatable>table{width:100%;display:block}.custom-fields-component :where(.fi-in-table-repeatable>table>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component .fi-in-table-repeatable>table{border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-table-repeatable>table{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component :where(.fi-in-table-repeatable>table:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-in-table-repeatable>table:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-in-table-repeatable>table:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-table-repeatable>table:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-in-table-repeatable>table>thead{white-space:nowrap;display:none}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th{border-color:var(--gray-200);background-color:var(--gray-50);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);text-align:start;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:first-of-type{border-start-start-radius:var(--radius-xl)}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:last-of-type{border-start-end-radius:var(--radius-xl)}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th.fi-align-center{text-align:center}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th.fi-align-end,.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th.fi-align-right{text-align:end}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th.fi-wrapped{white-space:normal}.custom-fields-component .fi-in-table-repeatable>table>thead>tr>th:not(.fi-wrapped){white-space:nowrap}.custom-fields-component .fi-in-table-repeatable>table>tbody{display:block}.custom-fields-component :where(.fi-in-table-repeatable>table>tbody>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-in-table-repeatable>table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-in-table-repeatable>table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr{gap:calc(var(--spacing)*6);padding:calc(var(--spacing)*6);display:grid}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td{display:block}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td.fi-hidden{display:none}@supports (container-type:inline-size){.custom-fields-component .fi-in-table-repeatable{container-type:inline-size}@container (min-width:36rem){.custom-fields-component .fi-in-table-repeatable>table{display:table}.custom-fields-component .fi-in-table-repeatable>table>thead{display:table-header-group}.custom-fields-component .fi-in-table-repeatable>table>tbody{display:table-row-group}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr{padding:0;display:table-row}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);display:table-cell}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td.fi-hidden{display:table-cell}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td .fi-in-entry{row-gap:0}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td .fi-in-entry-label{display:none}}}@supports not (container-type:inline-size){@media (min-width:64rem){.custom-fields-component .fi-in-table-repeatable>table{display:table}.custom-fields-component .fi-in-table-repeatable>table>thead{display:table-header-group}.custom-fields-component .fi-in-table-repeatable>table>tbody{display:table-row-group}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr{padding:0;display:table-row}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);display:table-cell}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td.fi-hidden{display:table-cell}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td .fi-in-entry{row-gap:0}.custom-fields-component .fi-in-table-repeatable>table>tbody>tr>td .fi-in-entry-label{display:none}}}.custom-fields-component .fi-in-text{width:100%}.custom-fields-component .fi-in-text.fi-numeric{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.custom-fields-component .fi-in-text.fi-in-text-affixed{gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-in-text .fi-in-text-affixed-content{flex:1;min-width:0}.custom-fields-component .fi-in-text .fi-in-text-affix{align-items:center;gap:calc(var(--spacing)*3);align-self:stretch;display:flex}.custom-fields-component .fi-in-text.fi-in-text-list-limited{flex-direction:column;display:flex}.custom-fields-component .fi-in-text.fi-in-text-list-limited.fi-in-text-has-badges{row-gap:calc(var(--spacing)*2)}.custom-fields-component .fi-in-text.fi-in-text-list-limited:not(.fi-in-text-has-badges){row-gap:var(--spacing)}.custom-fields-component .fi-in-text.fi-bulleted ul,.custom-fields-component ul.fi-in-text.fi-bulleted{list-style-type:disc;list-style-position:inside}.custom-fields-component .fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges ul,.custom-fields-component ul.fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges{column-gap:calc(var(--spacing)*1.5);display:flex}.custom-fields-component :is(ul.fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges,.fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges ul).fi-wrapped,.custom-fields-component :is(ul.fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges,.fi-in-text:not(.fi-in-text-has-line-breaks).fi-in-text-has-badges ul):is(.fi-wrapped ul){row-gap:var(--spacing);flex-wrap:wrap}.custom-fields-component :is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul).fi-in-text-has-line-breaks,.custom-fields-component :is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):is(.fi-in-text-has-line-breaks ul){row-gap:var(--spacing);flex-direction:column;display:flex}.custom-fields-component :is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(.fi-in-text-has-line-breaks ul),.custom-fields-component :is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(ul.fi-in-text-has-line-breaks){column-gap:calc(var(--spacing)*1.5);display:flex}.custom-fields-component :is(:is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(ul.fi-in-text-has-line-breaks),:is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(.fi-in-text-has-line-breaks ul)).fi-wrapped,.custom-fields-component :is(:is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(ul.fi-in-text-has-line-breaks),:is(ul.fi-in-text.fi-in-text-has-badges,.fi-in-text.fi-in-text-has-badges ul):not(.fi-in-text-has-line-breaks ul)):is(.fi-wrapped ul){row-gap:var(--spacing);flex-wrap:wrap}.custom-fields-component .fi-in-text.fi-wrapped:not(.fi-in-text-has-badges.fi-in-text-has-line-breaks){white-space:normal;overflow-wrap:break-word}.custom-fields-component .fi-in-text.fi-wrapped:not(.fi-in-text-has-badges.fi-in-text-has-line-breaks) .fi-badge,.custom-fields-component .fi-in-text.fi-wrapped:not(.fi-in-text-has-badges.fi-in-text-has-line-breaks) .fi-in-text-list-limited-message{white-space:nowrap}.custom-fields-component .fi-in-text>.fi-in-text-list-limited-message{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-in-text>.fi-in-text-list-limited-message:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-in-text.fi-align-center{text-align:center}.custom-fields-component .fi-in-text.fi-align-center ul,.custom-fields-component ul.fi-in-text.fi-align-center{justify-content:center}.custom-fields-component .fi-in-text.fi-align-end,.custom-fields-component .fi-in-text.fi-align-right{text-align:end}.custom-fields-component :is(.fi-in-text.fi-align-end,.fi-in-text.fi-align-right) ul,.custom-fields-component ul:is(.fi-in-text.fi-align-end,.fi-in-text.fi-align-right){justify-content:flex-end}.custom-fields-component .fi-in-text.fi-align-between,.custom-fields-component .fi-in-text.fi-align-justify{text-align:justify}.custom-fields-component :is(.fi-in-text.fi-align-justify,.fi-in-text.fi-align-between) ul,.custom-fields-component ul:is(.fi-in-text.fi-align-justify,.fi-in-text.fi-align-between){justify-content:space-between}.custom-fields-component .fi-in-text-item{color:var(--gray-950)}.custom-fields-component .fi-in-text-item:where(.dark,.dark *){color:var(--color-white)}@media (hover:hover){.custom-fields-component .fi-in-text-item a:hover{text-decoration-line:underline}}.custom-fields-component .fi-in-text-item a:focus-visible{text-decoration-line:underline}.custom-fields-component .fi-in-text-item:not(.fi-bulleted li.fi-in-text-item){-webkit-line-clamp:var(--line-clamp,none);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.custom-fields-component .fi-in-text-item>.fi-copyable{cursor:pointer;border-radius:var(--radius-md);--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-in-text-item>.fi-copyable:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component .fi-in-text-item>.fi-copyable:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-in-text-item.fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-in-text-item.fi-size-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-in-text-item.fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-in-text-item.fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-in-text-item.fi-font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.custom-fields-component .fi-in-text-item.fi-font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.custom-fields-component .fi-in-text-item.fi-font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.custom-fields-component .fi-in-text-item.fi-font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.custom-fields-component .fi-in-text-item.fi-font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-in-text-item.fi-font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-in-text-item.fi-font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.custom-fields-component .fi-in-text-item.fi-font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.custom-fields-component .fi-in-text-item.fi-font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.custom-fields-component .fi-in-text-item.fi-font-sans{font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.custom-fields-component .fi-in-text-item.fi-font-serif{font-family:var(--serif-font-family),ui-serif,Georgia,Cambria,"Times New Roman",Times,serif}.custom-fields-component .fi-in-text-item.fi-font-mono{font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.custom-fields-component .fi-in-text-item.fi-color{color:var(--text)}.custom-fields-component .fi-in-text-item.fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component li.fi-in-text-item.fi-color::marker{color:var(--gray-950)}.custom-fields-component li.fi-in-text-item.fi-color:where(.dark,.dark *)::marker{color:var(--color-white)}.custom-fields-component .fi-in-text-item.fi-color-gray{color:var(--gray-500)}.custom-fields-component .fi-in-text-item.fi-color-gray:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component li.fi-in-text-item.fi-color-gray::marker{color:var(--gray-950)}.custom-fields-component .fi-in-text-item>.fi-icon,.custom-fields-component .fi-in-text-item>span:not(.fi-badge)>.fi-icon{color:var(--gray-400);flex-shrink:0;display:inline-block}.custom-fields-component :is(.fi-in-text-item>.fi-icon,.fi-in-text-item>span:not(.fi-badge)>.fi-icon):where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component :is(.fi-in-text-item>.fi-icon,.fi-in-text-item>span:not(.fi-badge)>.fi-icon).fi-color{color:var(--color-500)}.custom-fields-component .fi-no-database{display:flex}.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-heading{display:inline-block;position:relative}.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-heading .fi-badge{inset-inline-start:100%;top:calc(var(--spacing)*-1);width:max-content;margin-inline-start:var(--spacing);position:absolute}.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-header .fi-ac{margin-top:calc(var(--spacing)*2)}.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-content{row-gap:0;padding:0}.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-content .fi-no-notifications{margin-bottom:-1px}.custom-fields-component :where(.fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-content .fi-no-notifications>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-content .fi-no-notifications:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-content .fi-no-notifications:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-content .fi-no-notifications>:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200)}.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-content .fi-no-notifications>:last-child:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-content .fi-no-notifications>:last-child:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-footer .fi-modal-content{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200)}.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-footer .fi-modal-content:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window.fi-modal-window-has-footer .fi-modal-content:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-no-database.fi-modal .fi-modal-window-ctn>.fi-modal-window .fi-modal-footer{padding-top:calc(var(--spacing)*6)}.custom-fields-component .fi-no-database .fi-no-notification-unread-ctn{position:relative}.custom-fields-component .fi-no-database .fi-no-notification-unread-ctn:before{height:100%;width:calc(var(--spacing)*.5);content:var(--tw-content);background-color:var(--primary-600);position:absolute;inset-inline-start:calc(var(--spacing)*0)}.custom-fields-component .fi-no-database .fi-no-notification-unread-ctn:where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--primary-500)}.custom-fields-component .fi-no-notification{pointer-events:auto;visibility:hidden;gap:calc(var(--spacing)*3);width:100%;padding:calc(var(--spacing)*4);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.3s;flex-shrink:0;transition-duration:.3s;display:flex;overflow:hidden}.custom-fields-component .fi-no-notification .fi-no-notification-icon{color:var(--gray-400)}.custom-fields-component .fi-no-notification .fi-no-notification-icon.fi-color{color:var(--color-400)}.custom-fields-component .fi-no-notification .fi-no-notification-main{margin-top:calc(var(--spacing)*.5);gap:calc(var(--spacing)*3);flex:1;display:grid}.custom-fields-component .fi-no-notification .fi-no-notification-text{gap:var(--spacing);display:grid}.custom-fields-component .fi-no-notification .fi-no-notification-title{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-no-notification .fi-no-notification-title:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-no-notification .fi-no-notification-date{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-no-notification .fi-no-notification-date:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-no-notification .fi-no-notification-body{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));text-wrap:pretty;overflow-wrap:break-word;color:var(--gray-500);overflow:hidden}.custom-fields-component .fi-no-notification .fi-no-notification-body:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-no-notification .fi-no-notification-body>p:not(:first-of-type){margin-top:var(--spacing)}.custom-fields-component .fi-no-notification:not(.fi-inline){max-width:var(--container-sm);gap:calc(var(--spacing)*3);border-radius:var(--radius-xl);background-color:var(--color-white);padding:calc(var(--spacing)*4);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);display:flex}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification:not(.fi-inline){--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-no-notification:not(.fi-inline):where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification:not(.fi-inline):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-no-notification:not(.fi-inline).fi-color{--tw-ring-color:var(--color-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification:not(.fi-inline).fi-color{--tw-ring-color:color-mix(in oklab, var(--color-600) 20%, transparent)}}.custom-fields-component .fi-no-notification:not(.fi-inline).fi-color:where(.dark,.dark *){--tw-ring-color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification:not(.fi-inline).fi-color:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-400) 30%, transparent)}}.custom-fields-component .fi-no-notification:not(.fi-inline).fi-transition-leave-end{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.custom-fields-component .fi-no-notification.fi-color{background-color:#fff}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification.fi-color{background-color:color-mix(in oklab,#fff 90%,var(--color-400))}}.custom-fields-component .fi-no-notification.fi-color:where(.dark,.dark *){background-color:var(--gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification.fi-color:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-900) 90%,var(--color-400))}}.custom-fields-component .fi-no-notification.fi-color .fi-no-notification-body{color:var(--gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification.fi-color .fi-no-notification-body{color:color-mix(in oklab,var(--gray-700) 75%,transparent)}}.custom-fields-component .fi-no-notification.fi-color .fi-no-notification-body:where(.dark,.dark *){color:var(--gray-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-no-notification.fi-color .fi-no-notification-body:where(.dark,.dark *){color:color-mix(in oklab,var(--gray-300) 75%,transparent)}}.custom-fields-component .fi-no-notification.fi-transition-enter-start,.custom-fields-component .fi-no-notification.fi-transition-leave-end{opacity:0}.custom-fields-component :is(.fi-no.fi-align-start,.fi-no.fi-align-left) .fi-no-notification.fi-transition-enter-start{--tw-translate-x:calc(var(--spacing)*-12);translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component :is(.fi-no.fi-align-end,.fi-no.fi-align-right) .fi-no-notification.fi-transition-enter-start{--tw-translate-x:calc(var(--spacing)*12);translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-no.fi-align-center.fi-vertical-align-start .fi-no-notification.fi-transition-enter-start{--tw-translate-y:calc(var(--spacing)*-12);translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-no.fi-align-center.fi-vertical-align-end .fi-no-notification.fi-transition-enter-start{--tw-translate-y:calc(var(--spacing)*12);translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-no{pointer-events:none;inset:calc(var(--spacing)*4);z-index:50;gap:calc(var(--spacing)*3);margin-inline:auto;display:flex;position:fixed}.custom-fields-component .fi-no.fi-align-left,.custom-fields-component .fi-no.fi-align-start{align-items:flex-start}.custom-fields-component .fi-no.fi-align-center{align-items:center}.custom-fields-component .fi-no.fi-align-end,.custom-fields-component .fi-no.fi-align-right{align-items:flex-end}.custom-fields-component .fi-no.fi-vertical-align-start{flex-direction:column-reverse;justify-content:flex-end}.custom-fields-component .fi-no.fi-vertical-align-center{flex-direction:column;justify-content:center}.custom-fields-component .fi-no.fi-vertical-align-end{flex-direction:column;justify-content:flex-end}.custom-fields-component .fi-sc-actions{gap:calc(var(--spacing)*2);flex-direction:column;height:100%;display:flex}.custom-fields-component .fi-sc-actions .fi-sc-actions-label-ctn{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-sc-actions .fi-sc-actions-label-ctn .fi-sc-actions-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-sc-actions .fi-sc-actions-label-ctn .fi-sc-actions-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sc-actions.fi-sticky .fi-ac{margin-inline:calc(var(--spacing)*-4);width:100%;transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);background-color:var(--color-white);padding:calc(var(--spacing)*4);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);position:fixed;bottom:0}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-actions.fi-sticky .fi-ac{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-sc-actions.fi-sticky .fi-ac{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (min-width:48rem){.custom-fields-component .fi-sc-actions.fi-sticky .fi-ac{bottom:calc(var(--spacing)*4);border-radius:var(--radius-xl)}}.custom-fields-component .fi-sc-actions.fi-sticky .fi-ac:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-actions.fi-sticky .fi-ac:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-sc-actions.fi-vertical-align-start{justify-content:flex-start}.custom-fields-component .fi-sc-actions.fi-vertical-align-center{justify-content:center}.custom-fields-component .fi-sc-actions.fi-vertical-align-end{justify-content:flex-end}.custom-fields-component .fi-sc-flex{gap:calc(var(--spacing)*6);display:flex}.custom-fields-component .fi-sc-flex.fi-align-left,.custom-fields-component .fi-sc-flex.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-sc-flex.fi-align-center{justify-content:center}.custom-fields-component .fi-sc-flex.fi-align-end,.custom-fields-component .fi-sc-flex.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-sc-flex.fi-align-between,.custom-fields-component .fi-sc-flex.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-sc-flex.fi-dense{gap:calc(var(--spacing)*3)}.custom-fields-component .fi-sc-flex>.fi-hidden{display:none}.custom-fields-component .fi-sc-flex>.fi-growable{flex:1;width:100%}.custom-fields-component .fi-sc-flex.fi-from-default{align-items:flex-start}.custom-fields-component .fi-sc-flex.fi-from-default.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-default.fi-vertical-align-end{align-items:flex-end}.custom-fields-component .fi-sc-flex.fi-from-sm{flex-direction:column}@media (min-width:40rem){.custom-fields-component .fi-sc-flex.fi-from-sm{flex-direction:row;align-items:flex-start}.custom-fields-component .fi-sc-flex.fi-from-sm.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-sm.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-sc-flex.fi-from-md{flex-direction:column}@media (min-width:48rem){.custom-fields-component .fi-sc-flex.fi-from-md{flex-direction:row;align-items:flex-start}.custom-fields-component .fi-sc-flex.fi-from-md.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-md.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-sc-flex.fi-from-lg{flex-direction:column}@media (min-width:64rem){.custom-fields-component .fi-sc-flex.fi-from-lg{flex-direction:row;align-items:flex-start}.custom-fields-component .fi-sc-flex.fi-from-lg.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-lg.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-sc-flex.fi-from-xl{flex-direction:column}@media (min-width:80rem){.custom-fields-component .fi-sc-flex.fi-from-xl{flex-direction:row;align-items:flex-start}.custom-fields-component .fi-sc-flex.fi-from-xl.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-xl.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-sc-flex.fi-from-2xl{flex-direction:column}@media (min-width:96rem){.custom-fields-component .fi-sc-flex.fi-from-2xl{flex-direction:row;align-items:flex-start}.custom-fields-component .fi-sc-flex.fi-from-2xl.fi-vertical-align-center{align-items:center}.custom-fields-component .fi-sc-flex.fi-from-2xl.fi-vertical-align-end{align-items:flex-end}}.custom-fields-component .fi-sc-form{gap:calc(var(--spacing)*6);flex-direction:column;display:flex}.custom-fields-component .fi-sc-form.fi-dense{gap:calc(var(--spacing)*3)}.custom-fields-component .fi-sc-fused-group>.fi-sc{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-fused-group>.fi-sc{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-sc-fused-group>.fi-sc:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component .fi-sc-fused-group>.fi-sc:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-fused-group>.fi-sc:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-sc-fused-group>.fi-sc:where(.dark,.dark *):focus-within{--tw-ring-color:var(--primary-500)}.custom-fields-component :where(.fi-sc-fused-group .fi-sc>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-sc-fused-group .fi-sc>:not(:last-child)){border-color:color-mix(in oklab,var(--gray-950) 10%,transparent)}}.custom-fields-component .fi-sc-fused-group .fi-sc{border-radius:var(--radius-lg)}.custom-fields-component :where(.fi-sc-fused-group .fi-sc:where(.dark,.dark *)>:not(:last-child)){border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-sc-fused-group .fi-sc:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.custom-fields-component .fi-sc-fused-group .fi-sc .fi-sc-component,.custom-fields-component .fi-sc-fused-group .fi-sc .fi-sc-component .fi-fo-field,.custom-fields-component .fi-sc-fused-group .fi-sc .fi-sc-component .fi-input{min-height:100%}.custom-fields-component .fi-sc-fused-group .fi-sc .fi-sc-component .fi-sc-actions{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2)}.custom-fields-component .fi-sc-fused-group .fi-sc>:first-child .fi-input-wrp{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc>:last-child .fi-input-wrp{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.custom-fields-component :where(.fi-sc-fused-group .fi-sc.fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}@media (min-width:40rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.sm\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.sm\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.sm\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@media (min-width:48rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.md\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.md\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.md\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@media (min-width:64rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.lg\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.lg\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.lg\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@media (min-width:80rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@media (min-width:96rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\32 xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\32 xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\32 xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@supports (container-type:inline-size){@container (min-width:16rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@3xs\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@3xs\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@3xs\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:18rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@2xs\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@2xs\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@2xs\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:20rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@xs\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@xs\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@xs\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:24rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@sm\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@sm\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@sm\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:28rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@md\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@md\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@md\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:32rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@lg\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@lg\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@lg\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:36rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:42rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@2xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@2xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@2xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:48rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@3xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@3xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@3xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:56rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@4xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@4xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@4xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:64rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@5xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@5xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@5xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:72rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@6xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@6xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@6xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@container (min-width:80rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\@7xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\@7xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\@7xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\!\@sm\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@sm\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@sm\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@media (min-width:48rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\!\@md\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@md\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@md\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@media (min-width:64rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\!\@lg\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@lg\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@lg\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@media (min-width:80rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\!\@xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}@media (min-width:96rem){.custom-fields-component :where(.fi-sc-fused-group .fi-sc.\!\@2xl\:fi-grid-cols>:not(:last-child)){--tw-divide-x-reverse:0;border-inline-style:var(--tw-border-style);border-inline-start-width:calc(1px*var(--tw-divide-x-reverse));border-inline-end-width:calc(1px*(1 - var(--tw-divide-x-reverse)));--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(0px*var(--tw-divide-y-reverse));border-bottom-width:calc(0px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@2xl\:fi-grid-cols>:first-child .fi-input-wrp{border-start-end-radius:0;border-end-start-radius:var(--radius-lg)}.custom-fields-component .fi-sc-fused-group .fi-sc.\!\@2xl\:fi-grid-cols>:last-child .fi-input-wrp{border-start-end-radius:var(--radius-lg);border-end-start-radius:0}}}.custom-fields-component .fi-sc-fused-group .fi-input-wrp{--tw-shadow:0 0 #0000;--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-radius:0}.custom-fields-component .fi-sc-fused-group .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)).fi-invalid:focus-within,.custom-fields-component .fi-sc-fused-group .fi-input-wrp:not(.fi-disabled):not(:has(.fi-ac-action:focus)):focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-sc-icon{color:var(--gray-400)}.custom-fields-component .fi-sc-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-sc-icon.fi-color{color:var(--color-500)}.custom-fields-component .fi-sc-icon.fi-color:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-sc-image{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--gray-300)}.custom-fields-component .fi-sc-image:where(.dark,.dark *){border-color:#0000}.custom-fields-component .fi-sc-image.fi-align-center{margin-inline:auto}.custom-fields-component .fi-sc-image.fi-align-end,.custom-fields-component .fi-sc-image.fi-align-right{margin-inline-start:auto}.custom-fields-component .fi-sc-section{gap:calc(var(--spacing)*2);flex-direction:column;display:flex}.custom-fields-component .fi-sc-section .fi-sc-section-label-ctn{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-sc-section .fi-sc-section-label-ctn .fi-sc-section-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-sc-section .fi-sc-section-label-ctn .fi-sc-section-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sc-tabs{flex-direction:column;display:flex}.custom-fields-component .fi-sc-tabs .fi-tabs.fi-invisible{visibility:hidden}.custom-fields-component .fi-sc-tabs .fi-sc-tabs-tab{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-sc-tabs .fi-sc-tabs-tab{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-sc-tabs .fi-sc-tabs-tab.fi-active{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-sc-tabs .fi-sc-tabs-tab:not(.fi-active){display:none}.custom-fields-component .fi-sc-tabs.fi-contained{border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-tabs.fi-contained{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-sc-tabs.fi-contained:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-tabs.fi-contained:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-sc-tabs.fi-contained .fi-sc-tabs-tab.fi-active{padding:calc(var(--spacing)*6);margin-top:0}.custom-fields-component .fi-sc-tabs.fi-vertical{flex-direction:row}.custom-fields-component .fi-sc-tabs.fi-vertical .fi-sc-tabs-tab.fi-active{margin-inline-start:calc(var(--spacing)*6);flex:1;margin-top:0}.custom-fields-component .fi-sc-text.fi-copyable{cursor:pointer;--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-sc-text.fi-copyable:focus-visible{border-radius:var(--radius-sm);outline-style:var(--tw-outline-style);outline-offset:2px;outline-width:2px;outline-color:currentColor}.custom-fields-component .fi-sc-text.fi-font-sans{font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.custom-fields-component .fi-sc-text.fi-font-serif{font-family:var(--serif-font-family),ui-serif,Georgia,Cambria,"Times New Roman",Times,serif}.custom-fields-component .fi-sc-text.fi-font-mono{font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.custom-fields-component .fi-sc-text:not(.fi-badge){font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));overflow-wrap:break-word;color:var(--gray-600);display:inline-block}.custom-fields-component .fi-sc-text:not(.fi-badge):where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-color-neutral{color:var(--gray-950)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-color-neutral:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-color:not(.fi-color-neutral){color:var(--text)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-color:not(.fi-color-neutral):where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-sc-text:not(.fi-badge).fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-sc-unordered-list{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));margin-inline-start:calc(var(--spacing)*3);list-style-type:disc}.custom-fields-component .fi-sc-unordered-list.fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-sc-unordered-list.fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-sc-unordered-list.fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-sc-wizard{flex-direction:column;display:flex}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header{display:grid}@media (min-width:48rem){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header{grid-auto-flow:column;overflow-x:auto}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step{display:flex;position:relative}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active){display:none}@media (min-width:48rem){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active){display:flex}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn{align-items:center;column-gap:calc(var(--spacing)*4);height:100%;padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*4);text-align:start;display:flex}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn{width:calc(var(--spacing)*10);height:calc(var(--spacing)*10);border-radius:3.40282e+38px;flex-shrink:0;justify-content:center;align-items:center;display:flex}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-number{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text{justify-items:start;display:grid}@media (min-width:48rem){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text{width:max-content;max-width:calc(var(--spacing)*60)}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-description{text-align:start;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-separator{height:100%;width:calc(var(--spacing)*5);color:var(--gray-200);display:none;position:absolute;inset-inline-end:calc(var(--spacing)*0)}@media (min-width:48rem){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-separator{display:block}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-separator:where(:dir(rtl),[dir=rtl],[dir=rtl] *){rotate:180deg}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-separator:where(.dark,.dark *){color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step .fi-sc-wizard-header-step-separator:where(.dark,.dark *){color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-completed .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn{background-color:var(--primary-600)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-completed .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn:where(.dark,.dark *){background-color:var(--primary-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-completed .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-icon{color:var(--color-white)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-completed .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label{color:var(--gray-950)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-completed .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn{border-style:var(--tw-border-style);border-width:2px}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed).fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-icon{color:var(--primary-600)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed).fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-icon:where(.dark,.dark *){color:var(--primary-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed):not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed):not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed):not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label{color:var(--gray-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-completed):not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn{border-color:var(--primary-600)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn:where(.dark,.dark *){border-color:var(--primary-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-number{color:var(--primary-600)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-number:where(.dark,.dark *){color:var(--primary-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label{color:var(--primary-700)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step.fi-active .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-text .fi-sc-wizard-header-step-label:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn{border-color:var(--gray-300)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn:where(.dark,.dark *){border-color:var(--gray-600)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-number{color:var(--gray-500)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-header .fi-sc-wizard-header-step:not(.fi-active) .fi-sc-wizard-header-step-btn .fi-sc-wizard-header-step-icon-ctn .fi-sc-wizard-header-step-number:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-step{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.custom-fields-component .fi-sc-wizard .fi-sc-wizard-step{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-step:not(.fi-active){display:none}.custom-fields-component .fi-sc-wizard:not(.fi-sc-wizard-header-hidden) .fi-sc-wizard-step.fi-active{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-footer{justify-content:space-between;align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-footer>.fi-hidden{display:none}.custom-fields-component .fi-sc-wizard .fi-sc-wizard-footer>.fi-disabled{pointer-events:none;opacity:.7}.custom-fields-component .fi-sc-wizard.fi-contained{border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-wizard.fi-contained{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-sc-wizard.fi-contained:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-wizard.fi-contained:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-sc-wizard.fi-contained .fi-sc-wizard-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200)}.custom-fields-component .fi-sc-wizard.fi-contained .fi-sc-wizard-header:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-wizard.fi-contained .fi-sc-wizard-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-sc-wizard.fi-contained .fi-sc-wizard-step.fi-active{padding:calc(var(--spacing)*6);margin-top:0}.custom-fields-component .fi-sc-wizard.fi-contained .fi-sc-wizard-footer{padding-inline:calc(var(--spacing)*6);padding-bottom:calc(var(--spacing)*6)}.custom-fields-component .fi-sc-wizard:not(.fi-contained) .fi-sc-wizard-header{border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-wizard:not(.fi-contained) .fi-sc-wizard-header{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-sc-wizard:not(.fi-contained) .fi-sc-wizard-header:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sc-wizard:not(.fi-contained) .fi-sc-wizard-header:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-sc-wizard:not(.fi-contained) .fi-sc-wizard-footer{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-sc{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .fi-sc.fi-inline{flex-wrap:wrap;flex-grow:1;align-items:center;display:flex}.custom-fields-component .fi-sc.fi-inline>.fi-growable{flex:1;width:100%}.custom-fields-component .fi-sc.fi-inline>.fi-sc-action:not(.fi-hidden){display:contents}.custom-fields-component .fi-sc.fi-sc-has-gap{gap:calc(var(--spacing)*6)}.custom-fields-component .fi-sc.fi-sc-has-gap.fi-sc-dense{gap:calc(var(--spacing)*3)}.custom-fields-component .fi-sc.fi-align-left,.custom-fields-component .fi-sc.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-sc.fi-align-center{justify-content:center}.custom-fields-component .fi-sc.fi-align-end,.custom-fields-component .fi-sc.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-sc.fi-align-between,.custom-fields-component .fi-sc.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-sc>.fi-hidden{display:none}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-3xs{max-width:var(--container-3xs)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-2xs{max-width:var(--container-2xs)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-xs{max-width:var(--container-xs)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-sm{max-width:var(--container-sm)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-md{max-width:var(--container-md)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-lg{max-width:var(--container-lg)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-xl{max-width:var(--container-xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-2xl{max-width:var(--container-2xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-3xl{max-width:var(--container-3xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-4xl{max-width:var(--container-4xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-5xl{max-width:var(--container-5xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-6xl{max-width:var(--container-6xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-7xl{max-width:var(--container-7xl)}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-none{max-width:none}.custom-fields-component .fi-sc>.fi-grid-col.fi-width-container{width:100%}@media (min-width:40rem){.custom-fields-component .fi-sc>.fi-grid-col.fi-width-container{max-width:40rem}}@media (min-width:48rem){.custom-fields-component .fi-sc>.fi-grid-col.fi-width-container{max-width:48rem}}@media (min-width:64rem){.custom-fields-component .fi-sc>.fi-grid-col.fi-width-container{max-width:64rem}}@media (min-width:80rem){.custom-fields-component .fi-sc>.fi-grid-col.fi-width-container{max-width:80rem}}@media (min-width:96rem){.custom-fields-component .fi-sc>.fi-grid-col.fi-width-container{max-width:96rem}}.custom-fields-component .fi-sc>.fi-grid-col>.fi-sc-component{height:100%}.custom-fields-component fieldset.fi-sc-visibility-fieldset{border-style:var(--tw-border-style);min-width:0;border-width:0;min-inline-size:0;margin:0;padding:0}.custom-fields-component .fi-ta-actions{justify-content:flex-end;align-items:center;gap:calc(var(--spacing)*3);flex-shrink:0;max-width:100%;display:flex}.custom-fields-component .fi-ta-actions>*{flex-shrink:0}.custom-fields-component .fi-ta-actions.fi-wrapped{flex-wrap:wrap}@media (min-width:40rem){.custom-fields-component .fi-ta-actions.sm\:fi-not-wrapped{flex-wrap:nowrap}}.custom-fields-component .fi-ta-actions.fi-align-center{justify-content:center}.custom-fields-component .fi-ta-actions.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-ta-actions.fi-align-between{justify-content:space-between}@media (min-width:48rem){.custom-fields-component .fi-ta-actions.md\:fi-align-end{justify-content:flex-end}}.custom-fields-component .fi-ta-cell{padding:0}.custom-fields-component .fi-ta-cell:first-child{padding-inline-start:var(--spacing)}.custom-fields-component .fi-ta-cell:last-child{padding-inline-end:var(--spacing)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell:first-child{padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-cell:last-child{padding-inline-end:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-cell.fi-vertical-align-start{vertical-align:top}.custom-fields-component .fi-ta-cell.fi-vertical-align-end{vertical-align:bottom}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.sm\:fi-hidden{display:none}}@media (min-width:48rem){.custom-fields-component .fi-ta-cell.md\:fi-hidden{display:none}}@media (min-width:64rem){.custom-fields-component .fi-ta-cell.lg\:fi-hidden{display:none}}@media (min-width:80rem){.custom-fields-component .fi-ta-cell.xl\:fi-hidden{display:none}}@media (min-width:96rem){.custom-fields-component .fi-ta-cell.\32 xl\:fi-hidden{display:none}}.custom-fields-component .fi-ta-cell.sm\:fi-visible{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.sm\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-cell.md\:fi-visible{display:none}@media (min-width:48rem){.custom-fields-component .fi-ta-cell.md\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-cell.lg\:fi-visible{display:none}@media (min-width:64rem){.custom-fields-component .fi-ta-cell.lg\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-cell.xl\:fi-visible{display:none}@media (min-width:80rem){.custom-fields-component .fi-ta-cell.xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-cell.\32 xl\:fi-visible{display:none}@media (min-width:96rem){.custom-fields-component .fi-ta-cell.\32 xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-cell .fi-ta-col{text-align:start;justify-content:flex-start;width:100%;display:flex}.custom-fields-component .fi-ta-cell .fi-ta-col:disabled{pointer-events:none}.custom-fields-component .fi-ta-cell:has(.fi-ta-reorder-handle){width:var(--spacing);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-cell:has(.fi-ta-reorder-handle):first-of-type{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell:has(.fi-ta-reorder-handle):last-of-type{padding-inline-end:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell:has(.fi-ta-reorder-handle):first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell:has(.fi-ta-reorder-handle):last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell:has(.fi-ta-actions){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4);white-space:nowrap}.custom-fields-component .fi-ta-cell:has(.fi-ta-actions):first-of-type{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell:has(.fi-ta-actions):last-of-type{padding-inline-end:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell:has(.fi-ta-actions):first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell:has(.fi-ta-actions):last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell:has(.fi-ta-record-checkbox){width:var(--spacing);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell:has(.fi-ta-record-checkbox):first-of-type{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell:has(.fi-ta-record-checkbox):last-of-type{padding-inline-end:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell:has(.fi-ta-record-checkbox):first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell:has(.fi-ta-record-checkbox):last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell .fi-ta-placeholder{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);color:var(--gray-400)}.custom-fields-component .fi-ta-cell .fi-ta-placeholder:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-cell.fi-ta-summary-row-heading-cell{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.fi-ta-summary-row-heading-cell:first-child{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell.fi-ta-summary-row-heading-cell:last-child{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell.fi-ta-summary-row-heading-cell:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-cell.fi-align-start{text-align:start}.custom-fields-component .fi-ta-cell.fi-align-center{text-align:center}.custom-fields-component .fi-ta-cell.fi-align-end{text-align:end}.custom-fields-component .fi-ta-cell.fi-align-left{text-align:left}.custom-fields-component .fi-ta-cell.fi-align-right{text-align:right}.custom-fields-component .fi-ta-cell.fi-align-between,.custom-fields-component .fi-ta-cell.fi-align-justify{text-align:justify}.custom-fields-component .fi-ta-cell.fi-ta-summary-header-cell{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.fi-ta-summary-header-cell:first-child{padding-inline-start:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell.fi-ta-summary-header-cell:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-cell.fi-ta-summary-header-cell.fi-wrapped,.custom-fields-component .fi-ta-cell.fi-ta-summary-header-cell:not(.fi-wrapped){white-space:nowrap}.custom-fields-component .fi-ta-cell.fi-ta-individual-search-cell{min-width:calc(var(--spacing)*48);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2)}.custom-fields-component .fi-ta-cell .fi-ta-reorder-handle{cursor:move}.custom-fields-component .fi-ta-cell.fi-ta-selection-cell{width:var(--spacing);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell.fi-ta-selection-cell:first-of-type{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell.fi-ta-selection-cell:last-of-type{padding-inline-end:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.fi-ta-selection-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell.fi-ta-selection-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-cell.fi-ta-group-selection-cell{width:var(--spacing);padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-cell.fi-ta-group-selection-cell:first-of-type{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-cell.fi-ta-group-selection-cell:last-of-type{padding-inline-end:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-cell.fi-ta-group-selection-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-cell.fi-ta-group-selection-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-checkbox{width:100%}.custom-fields-component .fi-ta-checkbox:not(.fi-inline){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-checkbox{padding:0}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-checkbox{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-checkbox.fi-align-center{text-align:center}.custom-fields-component .fi-ta-checkbox.fi-align-end,.custom-fields-component .fi-ta-checkbox.fi-align-right{text-align:end}.custom-fields-component .fi-ta-color{gap:calc(var(--spacing)*1.5);width:100%;display:flex}.custom-fields-component .fi-ta-color.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-ta-color:not(.fi-inline){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-color{padding:0}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-color{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-color.fi-align-left,.custom-fields-component .fi-ta-color.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-ta-color.fi-align-center{justify-content:center}.custom-fields-component .fi-ta-color.fi-align-end,.custom-fields-component .fi-ta-color.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-ta-color.fi-align-between,.custom-fields-component .fi-ta-color.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-ta-color>.fi-ta-color-item{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6);border-radius:var(--radius-md);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-color>.fi-ta-color-item{--tw-ring-color:color-mix(in oklab, var(--gray-950) 10%, transparent)}}.custom-fields-component .fi-ta-color>.fi-ta-color-item:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-color>.fi-ta-color-item:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-ta-color>.fi-ta-color-item.fi-copyable{cursor:pointer;--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-ta-color>.fi-ta-color-item.fi-copyable:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component .fi-ta-color>.fi-ta-color-item.fi-copyable:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-ta-icon{gap:calc(var(--spacing)*1.5);width:100%;display:flex}.custom-fields-component .fi-ta-icon.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-ta-icon.fi-ta-icon-has-line-breaks{flex-direction:column}.custom-fields-component .fi-ta-icon:not(.fi-inline){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-icon{padding:0}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-icon{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-icon.fi-align-left,.custom-fields-component .fi-ta-icon.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-ta-icon.fi-align-center{justify-content:center}.custom-fields-component .fi-ta-icon.fi-align-end,.custom-fields-component .fi-ta-icon.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-ta-icon.fi-align-between,.custom-fields-component .fi-ta-icon.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-ta-icon>.fi-icon,.custom-fields-component .fi-ta-icon>a>.fi-icon{color:var(--gray-400)}.custom-fields-component :is(.fi-ta-icon>.fi-icon,.fi-ta-icon>a>.fi-icon):where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component :is(.fi-ta-icon>.fi-icon,.fi-ta-icon>a>.fi-icon).fi-color{color:var(--text)}.custom-fields-component :is(.fi-ta-icon>.fi-icon,.fi-ta-icon>a>.fi-icon).fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-ta-image{align-items:center;gap:calc(var(--spacing)*1.5);width:100%;display:flex}.custom-fields-component .fi-ta-image img{object-fit:cover;object-position:center;max-width:none}.custom-fields-component .fi-ta-image.fi-circular img{border-radius:3.40282e+38px}.custom-fields-component .fi-ta-image.fi-ta-image-ring .fi-ta-image-limited-remaining-text,.custom-fields-component .fi-ta-image.fi-ta-image-ring img{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--color-white)}.custom-fields-component :is(.fi-ta-image.fi-ta-image-ring img,.fi-ta-image.fi-ta-image-ring .fi-ta-image-limited-remaining-text):where(.dark,.dark *){--tw-ring-color:var(--gray-900)}.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-1 .fi-ta-image-limited-remaining-text,.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-1 img{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-2 .fi-ta-image-limited-remaining-text,.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-2 img{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-4 .fi-ta-image-limited-remaining-text,.custom-fields-component .fi-ta-image.fi-ta-image-ring.fi-ta-image-ring-4 img{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-1{column-gap:0}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-1*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-1*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-2{column-gap:0}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-2*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-2*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-3{column-gap:0}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-3*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-3*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-4{column-gap:0}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-4*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-4*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-5{column-gap:0}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-5*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-5*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-6{column-gap:0}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-6*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-6*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-7{column-gap:0}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-7>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-7*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-7*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-ta-image.fi-ta-image-overlap-8{column-gap:0}.custom-fields-component :where(.fi-ta-image.fi-ta-image-overlap-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing)*-8*var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing)*-8*(1 - var(--tw-space-x-reverse)))}.custom-fields-component .fi-ta-image.fi-wrapped{flex-wrap:wrap}.custom-fields-component .fi-ta-image:not(.fi-inline){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-image{padding:0}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-image{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-image.fi-align-left,.custom-fields-component .fi-ta-image.fi-align-start{justify-content:flex-start}.custom-fields-component .fi-ta-image.fi-align-center{justify-content:center}.custom-fields-component .fi-ta-image.fi-align-end,.custom-fields-component .fi-ta-image.fi-align-right{justify-content:flex-end}.custom-fields-component .fi-ta-image.fi-align-between,.custom-fields-component .fi-ta-image.fi-align-justify{justify-content:space-between}.custom-fields-component .fi-ta-image.fi-stacked .fi-ta-image-limited-remaining-text{background-color:var(--gray-100);border-radius:3.40282e+38px}.custom-fields-component .fi-ta-image.fi-stacked .fi-ta-image-limited-remaining-text:where(.dark,.dark *){background-color:var(--gray-800)}.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-500);justify-content:center;align-items:center;display:flex}.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text.fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text.fi-size-base,.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text.fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-ta-image .fi-ta-image-limited-remaining-text.fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-ta-select{width:100%;min-width:calc(var(--spacing)*48)}.custom-fields-component .fi-ta-select:not(.fi-inline){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-select{padding:0}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-select{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-text{width:100%}.custom-fields-component .fi-ta-text.fi-numeric{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.custom-fields-component .fi-ta-text.fi-ta-text-has-descriptions,.custom-fields-component .fi-ta-text.fi-ta-text-list-limited{flex-direction:column;display:flex}.custom-fields-component :is(.fi-ta-text.fi-ta-text-has-descriptions,.fi-ta-text.fi-ta-text-list-limited).fi-ta-text-has-badges{row-gap:calc(var(--spacing)*2)}.custom-fields-component :is(.fi-ta-text.fi-ta-text-has-descriptions,.fi-ta-text.fi-ta-text-list-limited):not(.fi-ta-text-has-badges){row-gap:var(--spacing)}.custom-fields-component .fi-ta-text:not(.fi-inline){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-text{padding:0}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-text{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-text.fi-bulleted ul,.custom-fields-component ul.fi-ta-text.fi-bulleted{list-style-type:disc;list-style-position:inside}.custom-fields-component .fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges ul,.custom-fields-component ul.fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges{column-gap:calc(var(--spacing)*1.5);display:flex}.custom-fields-component :is(ul.fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges,.fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges ul).fi-wrapped,.custom-fields-component :is(ul.fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges,.fi-ta-text:not(.fi-ta-text-has-line-breaks).fi-ta-text-has-badges ul):is(.fi-wrapped ul){row-gap:var(--spacing);flex-wrap:wrap}.custom-fields-component :is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul).fi-ta-text-has-line-breaks,.custom-fields-component :is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):is(.fi-ta-text-has-line-breaks ul){row-gap:var(--spacing);flex-direction:column;display:flex}.custom-fields-component :is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(.fi-ta-text-has-line-breaks ul),.custom-fields-component :is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(ul.fi-ta-text-has-line-breaks){column-gap:calc(var(--spacing)*1.5);display:flex}.custom-fields-component :is(:is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(ul.fi-ta-text-has-line-breaks),:is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(.fi-ta-text-has-line-breaks ul)).fi-wrapped,.custom-fields-component :is(:is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(ul.fi-ta-text-has-line-breaks),:is(ul.fi-ta-text.fi-ta-text-has-badges,.fi-ta-text.fi-ta-text-has-badges ul):not(.fi-ta-text-has-line-breaks ul)):is(.fi-wrapped ul){row-gap:var(--spacing);flex-wrap:wrap}.custom-fields-component .fi-ta-text.fi-wrapped:not(.fi-ta-text-has-badges.fi-ta-text-has-line-breaks){white-space:normal}.custom-fields-component .fi-ta-text.fi-wrapped:not(.fi-ta-text-has-badges.fi-ta-text-has-line-breaks) .fi-badge,.custom-fields-component .fi-ta-text.fi-wrapped:not(.fi-ta-text-has-badges.fi-ta-text-has-line-breaks) .fi-ta-text-list-limited-message{white-space:nowrap}.custom-fields-component .fi-ta-text>.fi-ta-text-description,.custom-fields-component .fi-ta-text>.fi-ta-text-list-limited-message{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component :is(.fi-ta-text>.fi-ta-text-description,.fi-ta-text>.fi-ta-text-list-limited-message):where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-text.fi-align-center{text-align:center}.custom-fields-component .fi-ta-text.fi-align-center ul,.custom-fields-component ul.fi-ta-text.fi-align-center{justify-content:center}.custom-fields-component .fi-ta-text.fi-align-end,.custom-fields-component .fi-ta-text.fi-align-right{text-align:end}.custom-fields-component :is(.fi-ta-text.fi-align-end,.fi-ta-text.fi-align-right) ul,.custom-fields-component ul:is(.fi-ta-text.fi-align-end,.fi-ta-text.fi-align-right){justify-content:flex-end}.custom-fields-component .fi-ta-text.fi-align-between,.custom-fields-component .fi-ta-text.fi-align-justify{text-align:justify}.custom-fields-component :is(.fi-ta-text.fi-align-justify,.fi-ta-text.fi-align-between) ul,.custom-fields-component ul:is(.fi-ta-text.fi-align-justify,.fi-ta-text.fi-align-between){justify-content:space-between}.custom-fields-component .fi-ta-text-item{color:var(--gray-950)}.custom-fields-component .fi-ta-text-item:where(.dark,.dark *){color:var(--color-white)}@media (hover:hover){.custom-fields-component .fi-ta-text-item a:hover{text-decoration-line:underline}}.custom-fields-component .fi-ta-text-item a:focus-visible{text-decoration-line:underline}.custom-fields-component .fi-ta-text-item:not(.fi-bulleted li.fi-ta-text-item){-webkit-line-clamp:var(--line-clamp,none);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.custom-fields-component .fi-ta-text-item>.fi-copyable{cursor:pointer;border-radius:var(--radius-md);--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-ta-text-item>.fi-copyable:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component .fi-ta-text-item>.fi-copyable:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-ta-text-item.fi-size-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .fi-ta-text-item.fi-size-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-text-item.fi-size-md{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .fi-ta-text-item.fi-size-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .fi-ta-text-item.fi-font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.custom-fields-component .fi-ta-text-item.fi-font-extralight{--tw-font-weight:var(--font-weight-extralight);font-weight:var(--font-weight-extralight)}.custom-fields-component .fi-ta-text-item.fi-font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.custom-fields-component .fi-ta-text-item.fi-font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.custom-fields-component .fi-ta-text-item.fi-font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .fi-ta-text-item.fi-font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-ta-text-item.fi-font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.custom-fields-component .fi-ta-text-item.fi-font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.custom-fields-component .fi-ta-text-item.fi-font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.custom-fields-component .fi-ta-text-item.fi-font-sans{font-family:var(--font-family),ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.custom-fields-component .fi-ta-text-item.fi-font-serif{font-family:var(--serif-font-family),ui-serif,Georgia,Cambria,"Times New Roman",Times,serif}.custom-fields-component .fi-ta-text-item.fi-font-mono{font-family:var(--mono-font-family),ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.custom-fields-component .fi-ta-text-item.fi-color{color:var(--text)}.custom-fields-component .fi-ta-text-item.fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component li.fi-ta-text-item.fi-color::marker{color:var(--gray-950)}.custom-fields-component li.fi-ta-text-item.fi-color:where(.dark,.dark *)::marker{color:var(--color-white)}.custom-fields-component .fi-ta-text-item.fi-color-gray{color:var(--gray-500)}.custom-fields-component .fi-ta-text-item.fi-color-gray:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component li.fi-ta-text-item.fi-color-gray::marker{color:var(--gray-950)}.custom-fields-component li.fi-ta-text-item.fi-color-gray:where(.dark,.dark *)::marker{color:var(--color-white)}.custom-fields-component .fi-ta-text-item>.fi-icon,.custom-fields-component .fi-ta-text-item>span:not(.fi-badge)>.fi-icon{color:var(--gray-400);flex-shrink:0;display:inline-block}.custom-fields-component :is(.fi-ta-text-item>.fi-icon,.fi-ta-text-item>span:not(.fi-badge)>.fi-icon):where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component :is(.fi-ta-text-item>.fi-icon,.fi-ta-text-item>span:not(.fi-badge)>.fi-icon).fi-color{color:var(--color-500)}.custom-fields-component .fi-ta-text-item.fi-ta-text-has-badges>.fi-badge{vertical-align:middle}@media (hover:hover){.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item:hover{text-decoration-line:underline}}.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item:focus-visible{text-decoration-line:underline}@media (hover:hover){.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item .fi-icon:hover{text-decoration-line:none}}.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item .fi-icon:focus-visible{text-decoration-line:none}@media (hover:hover){.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item>.fi-badge:hover{text-decoration-line:none}}.custom-fields-component .fi-ta-col-has-column-url .fi-ta-text-item>.fi-badge:focus-visible{text-decoration-line:none}.custom-fields-component .fi-ta-text-input{width:100%;min-width:calc(var(--spacing)*48)}.custom-fields-component .fi-ta-text-input:not(.fi-inline){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-text-input{padding:0}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-text-input{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-toggle{width:100%}.custom-fields-component .fi-ta-toggle:not(.fi-inline){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-toggle{padding:0}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-toggle{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-toggle.fi-align-center{text-align:center}.custom-fields-component .fi-ta-toggle.fi-align-end,.custom-fields-component .fi-ta-toggle.fi-align-right{text-align:end}.custom-fields-component .fi-ta-grid.fi-gap-sm{gap:var(--spacing)}@media (min-width:40rem){.custom-fields-component .fi-ta-grid.sm\:fi-gap-sm{gap:var(--spacing)}}@media (min-width:48rem){.custom-fields-component .fi-ta-grid.md\:fi-gap-sm{gap:var(--spacing)}}@media (min-width:64rem){.custom-fields-component .fi-ta-grid.lg\:fi-gap-sm{gap:var(--spacing)}}@media (min-width:80rem){.custom-fields-component .fi-ta-grid.xl\:fi-gap-sm{gap:var(--spacing)}}@media (min-width:96rem){.custom-fields-component .fi-ta-grid.\32 xl\:fi-gap-sm{gap:var(--spacing)}}.custom-fields-component .fi-ta-grid.fi-gap-lg{gap:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-ta-grid.sm\:fi-gap-lg{gap:calc(var(--spacing)*3)}}@media (min-width:48rem){.custom-fields-component .fi-ta-grid.md\:fi-gap-lg{gap:calc(var(--spacing)*3)}}@media (min-width:64rem){.custom-fields-component .fi-ta-grid.lg\:fi-gap-lg{gap:calc(var(--spacing)*3)}}@media (min-width:80rem){.custom-fields-component .fi-ta-grid.xl\:fi-gap-lg{gap:calc(var(--spacing)*3)}}@media (min-width:96rem){.custom-fields-component .fi-ta-grid.\32 xl\:fi-gap-lg{gap:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-panel{border-radius:var(--radius-lg);background-color:var(--gray-50);padding:calc(var(--spacing)*4);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-panel{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-ta-panel{--tw-ring-inset:inset}.custom-fields-component .fi-ta-panel:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-panel:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-panel:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-panel:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-ta-split{display:flex}.custom-fields-component .fi-ta-split.default\:fi-ta-split{align-items:center;gap:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-split.\32 xl\:fi-ta-split,.custom-fields-component .fi-ta-split.lg\:fi-ta-split,.custom-fields-component .fi-ta-split.md\:fi-ta-split,.custom-fields-component .fi-ta-split.sm\:fi-ta-split,.custom-fields-component .fi-ta-split.xl\:fi-ta-split{gap:calc(var(--spacing)*2);flex-direction:column}@media (min-width:40rem){.custom-fields-component .fi-ta-split.sm\:fi-ta-split{align-items:center;gap:calc(var(--spacing)*3);flex-direction:row}}@media (min-width:48rem){.custom-fields-component .fi-ta-split.md\:fi-ta-split{align-items:center;gap:calc(var(--spacing)*3);flex-direction:row}}@media (min-width:64rem){.custom-fields-component .fi-ta-split.lg\:fi-ta-split{align-items:center;gap:calc(var(--spacing)*3);flex-direction:row}}@media (min-width:80rem){.custom-fields-component .fi-ta-split.xl\:fi-ta-split{align-items:center;gap:calc(var(--spacing)*3);flex-direction:row}}@media (min-width:96rem){.custom-fields-component .fi-ta-split.\32 xl\:fi-ta-split{align-items:center;gap:calc(var(--spacing)*3);flex-direction:row}}.custom-fields-component .fi-ta-stack{flex-direction:column;display:flex}.custom-fields-component .fi-ta-stack.fi-align-left,.custom-fields-component .fi-ta-stack.fi-align-start{align-items:flex-start}.custom-fields-component .fi-ta-stack.fi-align-center{align-items:center}.custom-fields-component .fi-ta-stack.fi-align-end,.custom-fields-component .fi-ta-stack.fi-align-right{align-items:flex-end}.custom-fields-component :where(.fi-ta-stack.fi-gap-sm>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing)*var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing)*(1 - var(--tw-space-y-reverse)))}.custom-fields-component :where(.fi-ta-stack.fi-gap-md>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing)*2*var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing)*2*(1 - var(--tw-space-y-reverse)))}.custom-fields-component :where(.fi-ta-stack.fi-gap-lg>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing)*3*var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing)*3*(1 - var(--tw-space-y-reverse)))}.custom-fields-component .fi-ta-icon-count-summary{row-gap:calc(var(--spacing)*1.5);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);display:grid}.custom-fields-component .fi-ta-icon-count-summary:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-icon-count-summary{padding-inline:0;padding-block:calc(var(--spacing)*2)}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-icon-count-summary{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-icon-count-summary>.fi-ta-icon-count-summary-label{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-ta-icon-count-summary>.fi-ta-icon-count-summary-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-icon-count-summary>ul{row-gap:calc(var(--spacing)*1.5);display:grid}.custom-fields-component .fi-ta-icon-count-summary>ul>li{align-items:center;column-gap:calc(var(--spacing)*1.5);display:flex}.custom-fields-component .fi-ta-icon-count-summary>ul>li>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-ta-icon-count-summary>ul>li>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-icon-count-summary>ul>li>.fi-icon.fi-color{color:var(--text)}.custom-fields-component .fi-ta-icon-count-summary>ul>li>.fi-icon.fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-ta-range-summary{row-gap:var(--spacing);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);display:grid}.custom-fields-component .fi-ta-range-summary:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-range-summary{padding-inline:0;padding-block:calc(var(--spacing)*2)}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-range-summary{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-range-summary>.fi-ta-range-summary-label{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-ta-range-summary>.fi-ta-range-summary-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-text-summary{row-gap:var(--spacing);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);display:grid}.custom-fields-component .fi-ta-text-summary:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-text-summary{padding-inline:0;padding-block:calc(var(--spacing)*2)}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-text-summary{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-text-summary>.fi-ta-text-summary-label{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-ta-text-summary>.fi-ta-text-summary-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-values-summary{row-gap:var(--spacing);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);display:grid}.custom-fields-component .fi-ta-values-summary:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-values-summary{padding-inline:0;padding-block:calc(var(--spacing)*2)}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-on-mobile .fi-ta-values-summary{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}}.custom-fields-component .fi-ta-values-summary>.fi-ta-values-summary-label{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-ta-values-summary>.fi-ta-values-summary-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-values-summary>ul.fi-bulleted{list-style-type:disc;list-style-position:inside}.custom-fields-component .fi-ta-ctn{border-radius:var(--radius-xl);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);display:flex;position:relative}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-ta-ctn:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-ta-ctn:not(.fi-ta-ctn-with-header){overflow:hidden}.custom-fields-component .fi-ta-ctn.fi-loading{animation:var(--animate-pulse)}.custom-fields-component .fi-ta-ctn .fi-ta-header{gap:calc(var(--spacing)*3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);padding:calc(var(--spacing)*4);flex-direction:column;display:flex}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-header:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header.fi-ta-header-adaptive-actions-position{flex-direction:row;align-items:center}.custom-fields-component .fi-ta-ctn .fi-ta-header.fi-ta-header-adaptive-actions-position .fi-ta-actions{margin-inline-start:auto}}.custom-fields-component .fi-ta-ctn .fi-ta-header.fi-ta-header-adaptive-actions-position:not(:has(.fi-ta-header-heading)):not(:has(.fi-ta-header-description)) .fi-ta-actions{margin-inline-start:auto}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-actions:has(.fi-btn.fi-size-xs:not(.fi-dropdown-panel *)){margin-block:calc(var(--spacing)*-.5)}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-actions:has(.fi-btn.fi-size-sm:not(.fi-dropdown-panel *)){margin-block:calc(var(--spacing)*-1)}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-actions:has(.fi-btn.fi-size-md:not(.fi-dropdown-panel *)){margin-block:calc(var(--spacing)*-1.5)}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-actions:has(.fi-btn.fi-size-lg:not(.fi-dropdown-panel *)){margin-block:calc(var(--spacing)*-2)}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-actions:has(.fi-btn.fi-size-xl:not(.fi-dropdown-panel *)){margin-block:calc(var(--spacing)*-2.5)}}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-header-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-header-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-header-description{margin-top:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-600)}.custom-fields-component .fi-ta-ctn .fi-ta-header .fi-ta-header-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar{justify-content:space-between;align-items:center;gap:calc(var(--spacing)*4);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);flex-wrap:wrap;display:flex}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar>*{min-height:calc(var(--spacing)*9);align-items:center;column-gap:calc(var(--spacing)*4);display:flex}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar>:first-child{flex-shrink:0}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar>:nth-child(2){margin-inline-start:auto}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings .fi-dropdown.sm\:fi-hidden{display:none}}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings .fi-dropdown .fi-ta-grouping-settings-fields{row-gap:calc(var(--spacing)*6);padding:calc(var(--spacing)*6);display:grid}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings .fi-dropdown .fi-ta-grouping-settings-fields label{row-gap:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950);display:grid}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings .fi-dropdown .fi-ta-grouping-settings-fields label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings>.fi-ta-grouping-settings-fields{align-items:center;column-gap:calc(var(--spacing)*3);display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-grouping-settings>.fi-ta-grouping-settings-fields{display:flex}}.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-col-manager-dropdown .fi-ta-col-manager,.custom-fields-component .fi-ta-ctn .fi-ta-header-toolbar .fi-ta-filters-dropdown .fi-ta-filters{padding:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-ctn .fi-ta-filters{row-gap:calc(var(--spacing)*4);display:grid}.custom-fields-component .fi-ta-ctn .fi-ta-filters.fi-ta-filters-below-content{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-200);padding:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters.fi-ta-filters-below-content{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters.fi-ta-filters-below-content:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-filters.fi-ta-filters-below-content:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-header{justify-content:space-between;align-items:center;display:flex}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-header .fi-ta-filters-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-header .fi-ta-filters-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-header .fi-loading-indicator{color:var(--gray-400)}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-header .fi-loading-indicator:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-ctn .fi-ta-filters .fi-ta-filters-actions-ctn{gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*4);display:grid}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn .fi-ta-filters-trigger-action-ctn{margin-inline-start:auto}.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn.fi-open .fi-ta-filters-trigger-action-ctn{margin-top:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-ctn .fi-ta-filters-above-content-ctn.fi-open:has(.fi-ta-filters-actions-ctn) .fi-ta-filters-trigger-action-ctn{margin-top:calc(var(--spacing)*-7)}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator{align-items:center;column-gap:calc(var(--spacing)*3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);background-color:var(--gray-50);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-700);display:flex}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator .fi-loading-indicator{color:var(--gray-400)}.custom-fields-component .fi-ta-ctn .fi-ta-reorder-indicator .fi-loading-indicator:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator{justify-content:space-between;row-gap:var(--spacing);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);background-color:var(--gray-50);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);flex-direction:column;display:flex}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator{padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*1.5);flex-direction:row;align-items:center}}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator .fi-loading-indicator{color:var(--gray-400)}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator .fi-loading-indicator:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator .fi-ta-selection-indicator-actions-ctn,.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator>*{column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator>:first-child{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-700)}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator>:first-child:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-ta-ctn .fi-ta-selection-indicator>:nth-child(2){margin-inline-start:auto}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators{justify-content:space-between;align-items:flex-start;column-gap:calc(var(--spacing)*3);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);background-color:var(--gray-50);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*1.5);display:flex}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:first-child{column-gap:calc(var(--spacing)*3);row-gap:var(--spacing);flex-direction:column;display:flex}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:first-child{flex-direction:row}}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:first-child .fi-ta-filter-indicators-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--gray-700)}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:first-child .fi-ta-filter-indicators-label:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:first-child .fi-ta-filter-indicators-badges-ctn{gap:calc(var(--spacing)*1.5);flex-wrap:wrap;display:flex}.custom-fields-component .fi-ta-ctn .fi-ta-filter-indicators>:nth-child(2).fi-icon-btn{margin-top:calc(var(--spacing)*-1)}.custom-fields-component .fi-ta-ctn .fi-pagination{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-200);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-ta-ctn .fi-pagination{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-ctn .fi-pagination:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-pagination:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-table-loading-ctn{height:calc(var(--spacing)*32);justify-content:center;align-items:center;display:flex}.custom-fields-component .fi-ta-ctn .fi-ta-main{flex:1;min-width:0}@media (min-width:64rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters-trigger-action-ctn.lg\:fi-hidden{display:none}}.custom-fields-component .fi-ta-ctn .fi-ta-filters-after-content-ctn,.custom-fields-component .fi-ta-ctn .fi-ta-filters-before-content-ctn{z-index:20;border-radius:var(--radius-lg);border-color:var(--gray-200);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);width:100vw;--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);flex-shrink:0;display:none;position:absolute;max-width:14rem!important}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-ctn .fi-ta-filters-after-content-ctn,.custom-fields-component .fi-ta-ctn .fi-ta-filters-before-content-ctn{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters-after-content-ctn,.custom-fields-component .fi-ta-ctn .fi-ta-filters-before-content-ctn{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (min-width:64rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters-after-content-ctn,.custom-fields-component .fi-ta-ctn .fi-ta-filters-before-content-ctn{z-index:auto;--tw-shadow:0 0 #0000;--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);position:static}}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn):where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn):where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn):where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn) .fi-ta-filters{padding:calc(var(--spacing)*6)}@media (min-width:64rem){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn) .fi-ta-filters{padding-block:calc(var(--spacing)*4)}}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-open{display:block}@media (min-width:64rem){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).lg\:fi-open{display:block}}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-opacity-0{opacity:0}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-3xs{max-width:var(--container-3xs)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-2xs{max-width:var(--container-2xs)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-xs{max-width:var(--container-xs)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-sm{max-width:var(--container-sm)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-md{max-width:var(--container-md)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-lg{max-width:var(--container-lg)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-xl{max-width:var(--container-xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-2xl{max-width:var(--container-2xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-3xl{max-width:var(--container-3xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-4xl{max-width:var(--container-4xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-5xl{max-width:var(--container-5xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-6xl{max-width:var(--container-6xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-7xl{max-width:var(--container-7xl)!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-none{max-width:none!important}.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-container{width:100%!important}@media (min-width:40rem){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-container{max-width:40rem!important}}@media (min-width:48rem){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-container{max-width:48rem!important}}@media (min-width:64rem){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-container{max-width:64rem!important}}@media (min-width:80rem){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-container{max-width:80rem!important}}@media (min-width:96rem){.custom-fields-component :is(.fi-ta-ctn .fi-ta-filters-before-content-ctn,.fi-ta-ctn .fi-ta-filters-after-content-ctn).fi-width-container{max-width:96rem!important}}.custom-fields-component .fi-ta-ctn .fi-ta-filters-before-content-ctn{inset-inline-start:calc(var(--spacing)*0)}@media (min-width:64rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters-before-content-ctn{border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px;border-start-start-radius:var(--radius-xl);border-start-end-radius:0;border-end-end-radius:0;border-end-start-radius:var(--radius-xl)}}.custom-fields-component .fi-ta-ctn .fi-ta-filters-after-content-ctn{inset-inline-end:calc(var(--spacing)*0)}@media (min-width:64rem){.custom-fields-component .fi-ta-ctn .fi-ta-filters-after-content-ctn{border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px;border-start-start-radius:0;border-start-end-radius:var(--radius-xl);border-end-end-radius:var(--radius-xl);border-end-start-radius:0}}.custom-fields-component .fi-ta-content-ctn{position:relative}.custom-fields-component :where(.fi-ta-content-ctn>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component .fi-ta-content-ctn{overflow-x:auto}.custom-fields-component :where(.fi-ta-content-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-ta-content-ctn:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-content-ctn:where(.dark,.dark *){border-top-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn:where(.dark,.dark *){border-top-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header{align-items:center;gap:calc(var(--spacing)*4);column-gap:calc(var(--spacing)*6);background-color:var(--gray-50);padding-inline:calc(var(--spacing)*4);display:flex}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header .fi-ta-page-checkbox{margin-block:calc(var(--spacing)*4);flex-shrink:0}.custom-fields-component .fi-ta-content-ctn .fi-ta-content-header .fi-ta-sorting-settings{column-gap:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-ta-content-ctn:not(.fi-ta-ctn-with-footer .fi-ta-content-ctn){border-bottom-right-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.custom-fields-component .fi-ta-content-ctn:not(.fi-ta-ctn-with-header .fi-ta-content-ctn){border-top-style:var(--tw-border-style);border-top-width:0}.custom-fields-component .fi-ta-content-ctn .fi-ta-content{display:grid}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid{gap:calc(var(--spacing)*4);padding:calc(var(--spacing)*4)}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid{padding-inline:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid.fi-ta-content-grouped{padding-top:0}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header{margin-inline:calc(var(--spacing)*-4);border-block-style:var(--tw-border-style);border-block-width:1px;border-color:var(--gray-200)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header:first-child{border-top-style:var(--tw-border-style);border-top-width:0}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header{margin-inline:calc(var(--spacing)*-6)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header{width:calc(100% + 2rem)}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-group-header{width:calc(100% + 3rem)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record{border-radius:var(--radius-xl);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}@media (hover:hover){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-selected:where(.dark,.dark *){background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-selected:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-selected:where(.dark,.dark *){--tw-ring-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-selected:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:not(.fi-selected){background-color:var(--color-white)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:not(.fi-selected):where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:not(.fi-selected):where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:not(.fi-selected):where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record:not(.fi-selected):where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-ta-record-with-content-prefix .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-ta-record-with-content-prefix .fi-ta-record-content{padding-inline-start:calc(var(--spacing)*2)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-ta-record-with-content-suffix .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content.fi-ta-content-grid .fi-ta-record.fi-ta-record-with-content-suffix .fi-ta-record-content{padding-inline-end:calc(var(--spacing)*2)}.custom-fields-component :where(.fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid)>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid):where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid):where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}@media (hover:hover){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-clickable:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-selected:before{width:calc(var(--spacing)*.5);content:var(--tw-content);background-color:var(--primary-600);position:absolute;inset-block:0;inset-inline-start:calc(var(--spacing)*0)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-selected:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-selected:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-selected:where(.dark,.dark *):before{content:var(--tw-content);background-color:var(--primary-500)}@media (min-width:48rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record .fi-ta-record-content-ctn{flex-direction:row;align-items:center}}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record:not(.fi-ta-record-with-content-prefix) .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record:not(.fi-ta-record-with-content-prefix) .fi-ta-record-content{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record:not(.fi-ta-record-with-content-suffix) .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record:not(.fi-ta-record-with-content-suffix) .fi-ta-record-content{padding-inline-end:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-prefix{padding-inline-start:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-prefix .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-prefix .fi-ta-record-content{padding-inline-start:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-suffix{padding-inline-end:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-suffix .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record.fi-ta-record-with-content-suffix .fi-ta-record-content{padding-inline-end:calc(var(--spacing)*3)}@media (min-width:48rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content:not(.fi-ta-content-grid) .fi-ta-record .fi-ta-actions{padding-inline-start:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header{align-items:center;column-gap:calc(var(--spacing)*3);background-color:var(--gray-50);width:100%;padding-inline:var(--spacing);padding-block:calc(var(--spacing)*2);grid-column:1/-1;display:flex}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header{padding-inline:calc(var(--spacing)*3)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header.fi-collapsible{cursor:pointer}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header.fi-collapsible.fi-collapsed .fi-icon-btn{rotate:-180deg}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header .fi-ta-group-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header .fi-ta-group-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header .fi-ta-group-description{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header .fi-ta-group-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-group-header .fi-ta-group-checkbox{margin-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-table{grid-column:1/-1}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record{background-color:var(--color-white);height:100%;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;align-items:center;transition-duration:75ms;display:flex;position:relative}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-ta-record-with-content-prefix{padding-inline-start:var(--spacing)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-ta-record-with-content-suffix{padding-inline-end:var(--spacing)}@media (hover:hover){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-clickable:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-collapsed{display:none}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-selected{background-color:var(--gray-50)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-reorder-handle{margin-inline:var(--spacing);margin-block:calc(var(--spacing)*2)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-checkbox{margin-inline:calc(var(--spacing)*3);margin-block:calc(var(--spacing)*4);flex-shrink:0}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn{row-gap:calc(var(--spacing)*3);width:100%;height:100%;padding-block:calc(var(--spacing)*4);flex-direction:column;display:flex}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn>:first-child{flex:1}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content{width:100%;display:block}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col{text-align:start;justify-content:flex-start;display:flex}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col:disabled{pointer-events:none}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-growable{width:100%}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-center{text-align:center;justify-content:center}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-end{text-align:end;justify-content:flex-end}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-left{text-align:left;justify-content:flex-start}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-right{text-align:right;justify-content:flex-end}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-between,.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-ta-col.fi-align-justify{text-align:justify;justify-content:space-between}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content.fi-collapsible{margin-top:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .fi-growable{flex:1;width:100%}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .sm\:fi-hidden{display:none}}@media (min-width:48rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .md\:fi-hidden{display:none}}@media (min-width:64rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .lg\:fi-hidden{display:none}}@media (min-width:80rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .xl\:fi-hidden{display:none}}@media (min-width:96rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .\32 xl\:fi-hidden{display:none}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .sm\:fi-visible{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .sm\:fi-visible{display:block}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .md\:fi-visible{display:none}@media (min-width:48rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .md\:fi-visible{display:block}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .lg\:fi-visible{display:none}@media (min-width:64rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .lg\:fi-visible{display:block}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .xl\:fi-visible{display:none}@media (min-width:80rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .xl\:fi-visible{display:block}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .\32 xl\:fi-visible{display:none}@media (min-width:96rem){.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-content-ctn .fi-ta-record-content .\32 xl\:fi-visible{display:block}}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-record-collapse-btn{margin-inline:var(--spacing);margin-block:calc(var(--spacing)*2);flex-shrink:0}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record .fi-ta-actions.fi-ta-actions-before-columns-position{order:-9999}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record:not(.fi-ta-record-with-content-prefix) .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record:not(.fi-ta-record-with-content-prefix) .fi-ta-record-content{padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record:not(.fi-ta-record-with-content-suffix) .fi-ta-actions,.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record:not(.fi-ta-record-with-content-suffix) .fi-ta-record-content{padding-inline-end:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-content-ctn .fi-ta-content .fi-ta-record.fi-ta-record-collapsed .fi-ta-record-collapse-btn{rotate:180deg}.custom-fields-component .fi-ta-empty-state{padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*12)}.custom-fields-component .fi-ta-empty-state:not(.fi-ta-ctn-with-content-layout .fi-ta-empty-state){border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--gray-200)}.custom-fields-component .fi-ta-empty-state:not(.fi-ta-ctn-with-content-layout .fi-ta-empty-state):where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-empty-state:not(.fi-ta-ctn-with-content-layout .fi-ta-empty-state):where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-content{max-width:var(--container-lg);text-align:center;justify-items:center;margin-inline:auto;display:grid}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-icon-bg{margin-bottom:calc(var(--spacing)*4);background-color:var(--gray-100);padding:calc(var(--spacing)*3);border-radius:3.40282e+38px}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-icon-bg:where(.dark,.dark *){background-color:var(--gray-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-icon-bg:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-500) 20%,transparent)}}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-icon-bg .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-icon-bg .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-description{margin-top:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-ta-empty-state .fi-ta-empty-state-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-empty-state .fi-ta-actions{margin-top:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-header-cell{padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*3.5);text-align:start;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}@media (min-width:40rem){.custom-fields-component .fi-ta-header-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-header-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-header-cell:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-header-cell.fi-growable{width:100%}.custom-fields-component .fi-ta-header-cell.fi-grouped{border-color:var(--gray-200)}.custom-fields-component .fi-ta-header-cell.fi-grouped:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-header-cell.fi-grouped:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-header-cell.fi-grouped:not(:first-of-type){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.custom-fields-component .fi-ta-header-cell.fi-grouped:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-ta-header-cell.fi-align-center{text-align:center}.custom-fields-component .fi-ta-header-cell.fi-align-center .fi-ta-header-cell-sort-btn{justify-content:center}.custom-fields-component .fi-ta-header-cell.fi-align-end{text-align:end}.custom-fields-component .fi-ta-header-cell.fi-align-end .fi-ta-header-cell-sort-btn{justify-content:flex-end}.custom-fields-component .fi-ta-header-cell.fi-align-left{text-align:left}.custom-fields-component .fi-ta-header-cell.fi-align-left .fi-ta-header-cell-sort-btn{justify-content:flex-start}.custom-fields-component .fi-ta-header-cell.fi-align-left .fi-ta-header-cell-sort-btn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){flex-direction:row-reverse}.custom-fields-component .fi-ta-header-cell.fi-align-right{text-align:right}.custom-fields-component .fi-ta-header-cell.fi-align-right .fi-ta-header-cell-sort-btn{justify-content:flex-end}.custom-fields-component .fi-ta-header-cell.fi-align-right .fi-ta-header-cell-sort-btn:where(:dir(rtl),[dir=rtl],[dir=rtl] *){flex-direction:row-reverse}.custom-fields-component .fi-ta-header-cell.fi-align-between,.custom-fields-component .fi-ta-header-cell.fi-align-justify{text-align:justify}.custom-fields-component :is(.fi-ta-header-cell.fi-align-justify,.fi-ta-header-cell.fi-align-between) .fi-ta-header-cell-sort-btn{justify-content:space-between}.custom-fields-component .fi-ta-header-cell.fi-ta-header-cell-sorted .fi-icon{color:var(--gray-950)}.custom-fields-component .fi-ta-header-cell.fi-ta-header-cell-sorted .fi-icon:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-icon:where(.dark,.dark *),.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-ta-header-cell-sort-btn:hover .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-ta-header-cell-sort-btn:hover .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-ta-header-cell-sort-btn:focus-visible .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-ta-header-cell:not(.fi-ta-header-cell-sorted) .fi-ta-header-cell-sort-btn:focus-visible .fi-icon:where(.dark,.dark *){color:var(--gray-400)}@media (min-width:40rem){.custom-fields-component .fi-ta-header-cell.sm\:fi-hidden{display:none}}@media (min-width:48rem){.custom-fields-component .fi-ta-header-cell.md\:fi-hidden{display:none}}@media (min-width:64rem){.custom-fields-component .fi-ta-header-cell.lg\:fi-hidden{display:none}}@media (min-width:80rem){.custom-fields-component .fi-ta-header-cell.xl\:fi-hidden{display:none}}@media (min-width:96rem){.custom-fields-component .fi-ta-header-cell.\32 xl\:fi-hidden{display:none}}.custom-fields-component .fi-ta-header-cell.sm\:fi-visible{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-header-cell.sm\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-cell.md\:fi-visible{display:none}@media (min-width:48rem){.custom-fields-component .fi-ta-header-cell.md\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-cell.lg\:fi-visible{display:none}@media (min-width:64rem){.custom-fields-component .fi-ta-header-cell.lg\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-cell.xl\:fi-visible{display:none}@media (min-width:80rem){.custom-fields-component .fi-ta-header-cell.xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-cell.\32 xl\:fi-visible{display:none}@media (min-width:96rem){.custom-fields-component .fi-ta-header-cell.\32 xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-cell.fi-wrapped{white-space:normal}.custom-fields-component .fi-ta-header-cell:not(.fi-wrapped){white-space:nowrap}.custom-fields-component .fi-ta-header-cell .fi-ta-header-cell-sort-btn{cursor:pointer;appearance:none;justify-content:flex-start;align-items:center;column-gap:var(--spacing);border-radius:var(--radius-md);border-style:var(--tw-border-style);text-align:start;--tw-outline-style:none;background-color:#0000;border-width:0;outline-style:none;width:100%;padding:0;display:flex}.custom-fields-component .fi-ta-header-cell .fi-ta-header-cell-sort-btn:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component .fi-ta-header-cell .fi-ta-header-cell-sort-btn:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-ta-header-cell .fi-ta-header-cell-sort-btn{text-transform:inherit}.custom-fields-component .fi-ta-header-cell .fi-icon{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;flex-shrink:0;transition-duration:75ms}.custom-fields-component .fi-ta-header-cell .fi-loading-indicator{color:var(--gray-400)}.custom-fields-component .fi-ta-header-cell .fi-loading-indicator:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-ta-header-group-cell{border-color:var(--gray-200);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}@media (min-width:40rem){.custom-fields-component .fi-ta-header-group-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-header-group-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-header-group-cell:where(.dark,.dark *){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-header-group-cell:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-header-group-cell:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-header-group-cell:not(:first-of-type){border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.custom-fields-component .fi-ta-header-group-cell:not(:last-of-type){border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.custom-fields-component .fi-ta-header-group-cell.fi-align-start{text-align:start}.custom-fields-component .fi-ta-header-group-cell.fi-align-center{text-align:center}.custom-fields-component .fi-ta-header-group-cell.fi-align-end{text-align:end}.custom-fields-component .fi-ta-header-group-cell.fi-align-left{text-align:left}.custom-fields-component .fi-ta-header-group-cell.fi-align-right{text-align:right}.custom-fields-component .fi-ta-header-group-cell.fi-align-between,.custom-fields-component .fi-ta-header-group-cell.fi-align-justify{text-align:justify}@media (min-width:40rem){.custom-fields-component .fi-ta-header-group-cell.sm\:fi-hidden{display:none}}@media (min-width:48rem){.custom-fields-component .fi-ta-header-group-cell.md\:fi-hidden{display:none}}@media (min-width:64rem){.custom-fields-component .fi-ta-header-group-cell.lg\:fi-hidden{display:none}}@media (min-width:80rem){.custom-fields-component .fi-ta-header-group-cell.xl\:fi-hidden{display:none}}@media (min-width:96rem){.custom-fields-component .fi-ta-header-group-cell.\32 xl\:fi-hidden{display:none}}.custom-fields-component .fi-ta-header-group-cell.sm\:fi-visible{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-header-group-cell.sm\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-group-cell.md\:fi-visible{display:none}@media (min-width:48rem){.custom-fields-component .fi-ta-header-group-cell.md\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-group-cell.lg\:fi-visible{display:none}@media (min-width:64rem){.custom-fields-component .fi-ta-header-group-cell.lg\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-group-cell.xl\:fi-visible{display:none}@media (min-width:80rem){.custom-fields-component .fi-ta-header-group-cell.xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-group-cell.\32 xl\:fi-visible{display:none}@media (min-width:96rem){.custom-fields-component .fi-ta-header-group-cell.\32 xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-header-group-cell.fi-wrapped{white-space:normal}.custom-fields-component .fi-ta-header-group-cell:not(.fi-wrapped){white-space:nowrap}.custom-fields-component .fi-ta-empty-header-cell{width:var(--spacing)}@media (hover:hover){.custom-fields-component .fi-ta-row{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-ta-row.fi-clickable:hover{background-color:var(--gray-50)}.custom-fields-component .fi-ta-row.fi-clickable:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-row.fi-clickable:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-ta-row.fi-striped{background-color:var(--gray-50)}.custom-fields-component .fi-ta-row.fi-striped:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-row.fi-striped:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-row.fi-collapsed{display:none}.custom-fields-component .fi-ta-row.fi-ta-group-header-row>td{background-color:var(--gray-50)}.custom-fields-component .fi-ta-row.fi-ta-group-header-row>td:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-row.fi-ta-group-header-row>td:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-row .fi-ta-group-header-cell{padding-inline:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-ta-row .fi-ta-group-header-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-row .fi-ta-group-header-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-row .fi-ta-group-header{align-items:center;column-gap:calc(var(--spacing)*3);width:100%;padding-block:calc(var(--spacing)*2);display:flex}.custom-fields-component .fi-ta-row .fi-ta-group-header.fi-collapsible{cursor:pointer}.custom-fields-component .fi-ta-row .fi-ta-group-header.fi-collapsible.fi-collapsed .fi-icon-btn{rotate:-180deg}.custom-fields-component .fi-ta-row .fi-ta-group-header .fi-ta-group-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-ta-row .fi-ta-group-header .fi-ta-group-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-row .fi-ta-group-header .fi-ta-group-description{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-ta-row .fi-ta-group-header .fi-ta-group-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-row.fi-selected:not(.fi-striped){background-color:var(--gray-50)}.custom-fields-component .fi-ta-row.fi-selected:not(.fi-striped):where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-row.fi-selected:not(.fi-striped):where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-row.fi-selected>:first-child{--tw-inset-shadow:inset 2px 0 0 0 var(--tw-inset-shadow-color,var(--primary-600));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-ta-row.fi-selected>:first-child:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-inset-shadow:inset -2px 0 0 0 var(--tw-inset-shadow-color,var(--primary-600));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-ta-row.fi-selected>:first-child:where(.dark,.dark *){--tw-inset-shadow:inset 2px 0 0 0 var(--tw-inset-shadow-color,var(--primary-500));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-ta-row.fi-selected>:first-child:where(.dark,.dark *):where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-inset-shadow:inset -2px 0 0 0 var(--tw-inset-shadow-color,var(--primary-500));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .fi-ta-reordering .fi-ta-row:not(.fi-ta-row-not-reorderable){cursor:move}.custom-fields-component .fi-ta-table{table-layout:auto;width:100%}.custom-fields-component :where(.fi-ta-table>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component .fi-ta-table{text-align:start}.custom-fields-component :where(.fi-ta-table:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-ta-table:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile{display:block}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile{table-layout:auto;display:table}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>thead{display:block}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>thead{display:table-header-group}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>thead:not(:has(.fi-ta-table-stacked-header-row)){display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>thead:not(:has(.fi-ta-table-stacked-header-row)){display:table-header-group}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>thead>tr:not(.fi-ta-table-stacked-header-row){display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>thead>tr:not(.fi-ta-table-stacked-header-row){display:table-row}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>thead>tr>.fi-ta-header-cell{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>thead>tr>.fi-ta-header-cell{display:table-cell}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>thead>tr>.fi-ta-selection-cell{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>thead>tr>.fi-ta-selection-cell{display:table-cell}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody{white-space:normal;display:block}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody{white-space:nowrap;display:table-row-group}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr{padding-block:calc(var(--spacing)*2);display:block;position:relative}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr{padding-block:0;display:table-row;position:static}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-collapsed{display:none}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-selected:before{width:calc(var(--spacing)*.5);background-color:var(--primary-600);position:absolute;inset-block:0;inset-inline-start:calc(var(--spacing)*0)}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-selected:before{display:none}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-selected:before{content:""}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-selected:where(.dark,.dark *):before{background-color:var(--primary-500)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-selected>:first-child:before{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-selected>:first-child:before{display:block}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-ta-group-header-row{padding-block:0}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-ta-group-header-row>td{width:100%;display:block}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-ta-group-header-row>td{width:auto;display:table-cell}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr.fi-ta-summary-row{padding-block:0}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-selection-cell{inset-inline-end:calc(var(--spacing)*5);position:absolute;top:0}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-selection-cell{width:var(--spacing);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4);display:table-cell;position:static}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-selection-cell:first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-selection-cell:last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell){padding-inline:calc(var(--spacing)*4);display:block}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell){padding-inline:0;display:table-cell}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell):first-child{padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell):last-child{padding-inline-end:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).fi-ta-summary-row-heading-cell{padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).fi-ta-summary-row-heading-cell:first-child{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).fi-ta-summary-row-heading-cell:last-child{padding-inline-end:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).fi-ta-summary-header-cell{padding-inline:calc(var(--spacing)*3)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).fi-ta-summary-header-cell:first-child{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).sm\:fi-hidden{display:none}}@media (min-width:48rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).md\:fi-hidden{display:none}}@media (min-width:64rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).lg\:fi-hidden{display:none}}@media (min-width:80rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).xl\:fi-hidden{display:none}}@media (min-width:96rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).\32 xl\:fi-hidden{display:none}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).sm\:fi-visible{display:none}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).sm\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).md\:fi-visible{display:none}@media (min-width:48rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).md\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).lg\:fi-visible{display:none}@media (min-width:64rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).lg\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).xl\:fi-visible{display:none}@media (min-width:80rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).\32 xl\:fi-visible{display:none}@media (min-width:96rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell).\32 xl\:fi-visible{display:table-cell}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell)>.fi-ta-cell-label{padding-top:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-500)}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell)>.fi-ta-cell-label{display:none}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell)>.fi-ta-cell-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell)>.fi-ta-cell-content{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));overflow-wrap:break-word;color:var(--gray-800)}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell)>.fi-ta-cell-content{display:block}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell)>.fi-ta-cell-content:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell):has(.fi-ta-actions){padding-block:calc(var(--spacing)*2)}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell):has(.fi-ta-actions){padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*4)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell):has(.fi-ta-actions):first-of-type{padding-inline-start:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell):has(.fi-ta-actions):last-of-type{padding-inline-end:calc(var(--spacing)*6)}}.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell):has(.fi-ta-actions)>.fi-ta-actions{justify-content:flex-start;column-gap:calc(var(--spacing)*3);row-gap:calc(var(--spacing)*2);flex-wrap:wrap;width:100%}@media (min-width:40rem){.custom-fields-component .fi-ta-table.fi-ta-table-stacked-on-mobile>tbody>tr>.fi-ta-cell:not(.fi-ta-selection-cell):has(.fi-ta-actions)>.fi-ta-actions{justify-content:flex-end;gap:calc(var(--spacing)*3);flex-wrap:nowrap;width:auto}}.custom-fields-component :where(.fi-ta-table>thead>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-ta-table>thead:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-ta-table>thead:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-table>thead>tr{background-color:var(--gray-50)}.custom-fields-component .fi-ta-table>thead>tr:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-table>thead>tr:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-table>thead>tr.fi-ta-table-head-groups-row{background-color:var(--gray-100)}.custom-fields-component .fi-ta-table>thead>tr.fi-ta-table-head-groups-row:where(.dark,.dark *){background-color:#0000}.custom-fields-component :where(.fi-ta-table>tbody>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component .fi-ta-table>tbody{white-space:nowrap}.custom-fields-component :where(.fi-ta-table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-ta-table>tbody:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-table>tfoot{background-color:var(--gray-50)}.custom-fields-component .fi-ta-table>tfoot:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-table>tfoot:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-table-stacked-header-row{border-block-style:var(--tw-border-style);border-block-width:0;width:100%;display:block}@media (min-width:40rem){.custom-fields-component .fi-ta-table-stacked-header-row{display:none}}.custom-fields-component .fi-ta-table-stacked-header-row .fi-ta-table-stacked-header-cell{align-items:center;gap:calc(var(--spacing)*4);background-color:var(--gray-50);width:100%;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*3);--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal);display:flex}.custom-fields-component .fi-ta-table-stacked-header-row .fi-ta-table-stacked-header-cell:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-ta-table-stacked-header-row .fi-ta-table-stacked-header-cell:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-ta-table-stacked-header-row .fi-ta-table-stacked-header-cell .fi-ta-page-checkbox{flex-shrink:0;margin-inline-start:auto}.custom-fields-component .fi-ta-table-stacked-header-row .fi-ta-table-stacked-header-cell .fi-ta-table-stacked-sorting{column-gap:calc(var(--spacing)*3);flex:1;display:flex}.custom-fields-component .fi-ta-col-manager{gap:calc(var(--spacing)*4);display:grid}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-header{justify-content:space-between;align-items:center;display:flex}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-items{margin-top:calc(var(--spacing)*-6);column-gap:calc(var(--spacing)*6)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-item{break-inside:avoid;align-items:center;gap:calc(var(--spacing)*3);padding-top:calc(var(--spacing)*6);display:flex}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-item .fi-ta-col-manager-label{align-items:center;column-gap:calc(var(--spacing)*3);width:100%;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950);flex:1;display:flex}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-item .fi-ta-col-manager-label:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-item .fi-ta-col-manager-label .fi-checkbox-input{flex-shrink:0}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-item .fi-ta-col-manager-reorder-handle{cursor:move}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-group{break-inside:avoid}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-group .fi-ta-col-manager-group-items{padding-inline-start:calc(var(--spacing)*8)}.custom-fields-component .fi-ta-col-manager .fi-ta-col-manager-actions-ctn{gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-wi-chart .fi-wi-chart-frame{justify-content:center;align-items:center;width:100%;margin-inline:auto;display:flex}.custom-fields-component .fi-wi-chart .fi-wi-chart-frame:not(.fi-wi-chart-frame-no-aspect-ratio){aspect-ratio:1}@supports (container-type:inline-size){.custom-fields-component .fi-wi-chart .fi-section-content{container-type:inline-size}@container (min-width:24rem){.custom-fields-component .fi-wi-chart .fi-wi-chart-frame:not(.fi-wi-chart-frame-no-aspect-ratio){aspect-ratio:1.5}}}@supports not (container-type:inline-size){@media (min-width:40rem){.custom-fields-component .fi-wi-chart .fi-wi-chart-frame:not(.fi-wi-chart-frame-no-aspect-ratio){aspect-ratio:1.5}}}.custom-fields-component .fi-wi-chart .fi-wi-chart-filter.fi-input-wrp{width:max-content}@media (min-width:40rem){.custom-fields-component .fi-wi-chart .fi-wi-chart-filter.fi-input-wrp{margin-block:calc(var(--spacing)*-2)}}.custom-fields-component .fi-wi-chart .fi-wi-chart-filter.fi-dropdown .fi-wi-chart-filter-content{row-gap:calc(var(--spacing)*4);padding:calc(var(--spacing)*6);display:grid}.custom-fields-component .fi-wi-chart .fi-wi-chart-filter.fi-dropdown .fi-wi-chart-filter-content-actions-ctn{gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-wi-chart .fi-color .fi-wi-chart-bg-color{color:var(--color-50)}.custom-fields-component .fi-wi-chart .fi-color .fi-wi-chart-bg-color:where(.dark,.dark *){color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-wi-chart .fi-color .fi-wi-chart-bg-color:where(.dark,.dark *){color:color-mix(in oklab,var(--color-400) 10%,transparent)}}.custom-fields-component .fi-wi-chart .fi-color .fi-wi-chart-border-color{color:var(--color-500)}.custom-fields-component .fi-wi-chart .fi-color .fi-wi-chart-border-color:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-wi-chart .fi-wi-chart-bg-color{color:var(--gray-100)}.custom-fields-component .fi-wi-chart .fi-wi-chart-bg-color:where(.dark,.dark *){color:var(--gray-800)}.custom-fields-component .fi-wi-chart .fi-wi-chart-border-color{color:var(--gray-400)}.custom-fields-component .fi-wi-chart .fi-wi-chart-grid-color{color:var(--gray-200)}.custom-fields-component .fi-wi-chart .fi-wi-chart-grid-color:where(.dark,.dark *){color:var(--gray-800)}.custom-fields-component .fi-wi-chart .fi-wi-chart-text-color{color:var(--gray-500)}.custom-fields-component .fi-wi-chart .fi-wi-chart-text-color:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-wi-chart .fi-wi-chart-tooltip-bg-color{color:#000c}.custom-fields-component .fi-wi-chart .fi-wi-chart-tooltip-text-color{color:#fff}.custom-fields-component .fi-wi-chart .fi-wi-chart-tooltip-border-color{color:#fff3}.custom-fields-component .fi-wi-chart .fi-empty-state{padding:0}.custom-fields-component .fi-wi-stats-overview-stat{border-radius:var(--radius-xl);background-color:var(--color-white);height:100%;padding:calc(var(--spacing)*6);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);display:block;position:relative}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-wi-stats-overview-stat{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-wi-stats-overview-stat{--tw-outline-style:none;outline-style:none}.custom-fields-component .fi-wi-stats-overview-stat:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}.custom-fields-component .fi-wi-stats-overview-stat:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-wi-stats-overview-stat:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-wi-stats-overview-stat:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-wi-stats-overview-stat .fi-icon{color:var(--gray-400);flex-shrink:0}.custom-fields-component .fi-wi-stats-overview-stat .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-content{row-gap:calc(var(--spacing)*2);display:grid}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-label-ctn{align-items:center;column-gap:calc(var(--spacing)*2);display:flex}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-500)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-value{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--gray-950);--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-value:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-description{align-items:center;column-gap:var(--spacing);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500);display:flex}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-description:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-description.fi-color{color:var(--text)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-description.fi-color:where(.dark,.dark *){color:var(--dark-text)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-description.fi-color .fi-icon{color:var(--color-500)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart{border-bottom-right-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl);inset-inline:0;position:absolute;bottom:0;overflow:hidden}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart>canvas{height:calc(var(--spacing)*6)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart .fi-wi-stats-overview-stat-chart-bg-color{color:var(--gray-100)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart .fi-wi-stats-overview-stat-chart-bg-color:where(.dark,.dark *){color:var(--gray-800)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart .fi-wi-stats-overview-stat-chart-border-color{color:var(--gray-400)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart.fi-color .fi-wi-stats-overview-stat-chart-bg-color{color:var(--color-50)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart.fi-color .fi-wi-stats-overview-stat-chart-bg-color:where(.dark,.dark *){color:var(--color-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart.fi-color .fi-wi-stats-overview-stat-chart-bg-color:where(.dark,.dark *){color:color-mix(in oklab,var(--color-400) 10%,transparent)}}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart.fi-color .fi-wi-stats-overview-stat-chart-border-color{color:var(--color-500)}.custom-fields-component .fi-wi-stats-overview-stat .fi-wi-stats-overview-stat-chart.fi-color .fi-wi-stats-overview-stat-chart-border-color:where(.dark,.dark *){color:var(--color-400)}.custom-fields-component .fi-wi{gap:calc(var(--spacing)*6)}.custom-fields-component .fi-global-search-ctn{align-items:center;display:flex}.custom-fields-component .fi-global-search{flex:1}@media (min-width:40rem){.custom-fields-component .fi-global-search{position:relative}}.custom-fields-component .fi-global-search-results-ctn{inset-inline:calc(var(--spacing)*4);z-index:10;margin-top:calc(var(--spacing)*2);max-height:calc(var(--spacing)*96);border-radius:var(--radius-lg);background-color:var(--color-white);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);position:absolute;overflow:auto}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-global-search-results-ctn{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-global-search-results-ctn{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (min-width:40rem){.custom-fields-component .fi-global-search-results-ctn{inset-inline:auto}}.custom-fields-component .fi-global-search-results-ctn:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-global-search-results-ctn:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-global-search-results-ctn{transform:translateZ(0)}.custom-fields-component .fi-global-search-results-ctn.fi-transition-enter-start,.custom-fields-component .fi-global-search-results-ctn.fi-transition-leave-end{opacity:0}@media (min-width:40rem){.custom-fields-component .fi-topbar .fi-global-search-results-ctn{width:100vw;max-width:var(--container-sm);inset-inline-end:calc(var(--spacing)*0)}}.custom-fields-component .fi-sidebar .fi-global-search-ctn{margin-inline:calc(var(--spacing)*3);margin-top:calc(var(--spacing)*3)}@media (min-width:40rem){.custom-fields-component .fi-sidebar .fi-global-search-results-ctn{inset-inline-start:calc(var(--spacing)*0)}}.custom-fields-component .fi-global-search-no-results-message{padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-global-search-no-results-message:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component :where(.fi-global-search-results>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-global-search-results:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-global-search-results:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-global-search-result-group-header{z-index:10;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--gray-200);background-color:var(--gray-50);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950);text-transform:capitalize;position:sticky;top:0}.custom-fields-component .fi-global-search-result-group-header:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-global-search-result-group-header:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-global-search-result-group-header:where(.dark,.dark *){background-color:var(--gray-800);color:var(--color-white)}.custom-fields-component :where(.fi-global-search-result-group-results>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-color:var(--gray-200)}.custom-fields-component :where(.fi-global-search-result-group-results:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.fi-global-search-result-group-results:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .fi-global-search-result{scroll-margin-top:calc(var(--spacing)*9);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-global-search-result:focus-within{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-global-search-result:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-global-search-result:where(.dark,.dark *):focus-within{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-global-search-result:where(.dark,.dark *):focus-within{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}@media (hover:hover){.custom-fields-component .fi-global-search-result:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-global-search-result:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-global-search-result.fi-global-search-result-has-actions .fi-global-search-result-link{padding-bottom:0}.custom-fields-component .fi-global-search-result-link{padding:calc(var(--spacing)*4);--tw-outline-style:none;outline-style:none;display:block}@media (forced-colors:active){.custom-fields-component .fi-global-search-result-link{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-global-search-result-heading{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950)}.custom-fields-component .fi-global-search-result-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-global-search-result-details{margin-top:var(--spacing)}.custom-fields-component .fi-global-search-result-detail{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-global-search-result-detail:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-global-search-result-detail-label{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);display:inline}.custom-fields-component .fi-global-search-result-detail-value{display:inline}.custom-fields-component .fi-global-search-result-actions{margin-top:calc(var(--spacing)*3);column-gap:calc(var(--spacing)*3);padding-inline:calc(var(--spacing)*4);padding-bottom:calc(var(--spacing)*4);display:flex}.custom-fields-component .fi-header{gap:calc(var(--spacing)*4);flex-direction:column;display:flex}@media (min-width:40rem){.custom-fields-component .fi-header{flex-direction:row;justify-content:space-between;align-items:center}}.custom-fields-component .fi-header .fi-breadcrumbs{margin-bottom:calc(var(--spacing)*2);display:none}@media (min-width:40rem){.custom-fields-component .fi-header .fi-breadcrumbs{display:block}.custom-fields-component .fi-header.fi-header-has-breadcrumbs .fi-header-actions-ctn{margin-top:calc(var(--spacing)*7)}}.custom-fields-component .fi-header-heading{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--gray-950)}@media (min-width:40rem){.custom-fields-component .fi-header-heading{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}}.custom-fields-component .fi-header-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-header-subheading{margin-top:calc(var(--spacing)*2);max-width:var(--container-2xl);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));color:var(--gray-600)}.custom-fields-component .fi-header-subheading:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-header-actions-ctn{align-items:center;gap:calc(var(--spacing)*3);flex-shrink:0;display:flex}.custom-fields-component .fi-header-actions-ctn>.fi-ac{flex:1}.custom-fields-component .fi-header-actions-ctn>.fi-ac:not(.fi-width-full).fi-align-end,.custom-fields-component .fi-header-actions-ctn>.fi-ac:not(.fi-width-full).fi-align-right{flex-direction:row;justify-content:flex-end}.custom-fields-component .fi-simple-header{flex-direction:column;align-items:center;display:flex}.custom-fields-component .fi-simple-header .fi-logo{margin-bottom:calc(var(--spacing)*4)}.custom-fields-component .fi-simple-header-heading{text-align:center;font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--gray-950)}.custom-fields-component .fi-simple-header-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-simple-header-subheading{margin-top:calc(var(--spacing)*2);text-align:center;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-simple-header-subheading:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component.fi{min-height:100dvh}.custom-fields-component .fi-body{background-color:var(--gray-50);--tw-font-weight:var(--font-weight-normal);min-height:100dvh;font-weight:var(--font-weight-normal);color:var(--gray-950);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.custom-fields-component .fi-body:where(.dark,.dark *){background-color:var(--gray-950);color:var(--color-white)}.custom-fields-component :is(.fi-body.fi-body-has-sidebar-collapsible-on-desktop,.fi-body.fi-body-has-sidebar-fully-collapsible-on-desktop) .fi-main-ctn{min-height:calc(100dvh - var(--topbar-height));opacity:0;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.custom-fields-component .fi-body>.fi-layout-sidebar-toggle-btn-ctn{padding-inline-start:calc(var(--spacing)*5);padding-top:calc(var(--spacing)*5)}@media (min-width:64rem){.custom-fields-component .fi-body>.fi-layout-sidebar-toggle-btn-ctn.lg\:fi-hidden{display:none}}.custom-fields-component .fi-body.fi-body-has-navigation:not(.fi-body-has-sidebar-collapsible-on-desktop):not(.fi-body-has-sidebar-fully-collapsible-on-desktop):not(.fi-body-has-top-navigation) .fi-main-ctn{opacity:0}.custom-fields-component :is(.fi-body.fi-body-has-top-navigation,.fi-body:not(.fi-body-has-navigation)) .fi-main-ctn{min-height:calc(100dvh - var(--topbar-height));display:flex}.custom-fields-component .fi-body:not(.fi-body-has-topbar) .fi-main-ctn{min-height:100dvh;display:flex}.custom-fields-component .fi-layout{width:100%;height:100%;display:flex;overflow-x:clip}.custom-fields-component .fi-main-ctn{flex-direction:column;flex:1;min-width:0}.custom-fields-component .fi-main{width:100%;height:100%;padding-inline:calc(var(--spacing)*4);margin-inline:auto}@media (min-width:48rem){.custom-fields-component .fi-main{padding-inline:calc(var(--spacing)*6)}}@media (min-width:64rem){.custom-fields-component .fi-main{padding-inline:calc(var(--spacing)*8)}}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-3xs{max-width:var(--container-3xs)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-2xs{max-width:var(--container-2xs)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-xs{max-width:var(--container-xs)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-sm{max-width:var(--container-sm)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-md{max-width:var(--container-md)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-lg{max-width:var(--container-lg)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-xl{max-width:var(--container-xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-2xl{max-width:var(--container-2xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-3xl{max-width:var(--container-3xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-4xl{max-width:var(--container-4xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-5xl{max-width:var(--container-5xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-6xl{max-width:var(--container-6xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-7xl{max-width:var(--container-7xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-none{max-width:none}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-full{max-width:100%}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-min{max-width:min-content}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-max{max-width:max-content}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-fit{max-width:fit-content}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-prose{max-width:65ch}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-container{width:100%}@media (min-width:40rem){.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-container{max-width:40rem}}@media (min-width:48rem){.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-container{max-width:48rem}}@media (min-width:64rem){.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-container{max-width:64rem}}@media (min-width:80rem){.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-container{max-width:80rem}}@media (min-width:96rem){.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-container{max-width:96rem}}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen-sm{max-width:var(--breakpoint-sm)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen-md{max-width:var(--breakpoint-md)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen-lg{max-width:var(--breakpoint-lg)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen-xl{max-width:var(--breakpoint-xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen-2xl{max-width:var(--breakpoint-2xl)}.custom-fields-component :is(.fi-main,.fi-simple-main).fi-width-screen{position:fixed;inset:0}.custom-fields-component .fi-simple-layout{flex-direction:column;align-items:center;min-height:100dvh;display:flex}.custom-fields-component .fi-simple-layout-header{height:calc(var(--spacing)*16);align-items:center;column-gap:calc(var(--spacing)*4);inset-inline-end:calc(var(--spacing)*0);padding-inline-end:calc(var(--spacing)*4);display:flex;position:absolute;top:0}@media (min-width:48rem){.custom-fields-component .fi-simple-layout-header{padding-inline-end:calc(var(--spacing)*6)}}@media (min-width:64rem){.custom-fields-component .fi-simple-layout-header{padding-inline-end:calc(var(--spacing)*8)}}.custom-fields-component .fi-simple-main-ctn{flex-grow:1;justify-content:center;align-items:center;width:100%;display:flex}.custom-fields-component .fi-simple-main{margin-block:calc(var(--spacing)*16);background-color:var(--color-white);width:100%;padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*12);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-simple-main{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}@media (min-width:40rem){.custom-fields-component .fi-simple-main{border-radius:var(--radius-xl);padding-inline:calc(var(--spacing)*12)}}.custom-fields-component .fi-simple-main:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-simple-main:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-logo{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5);--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);color:var(--gray-950);display:flex}.custom-fields-component .fi-logo:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-logo.fi-logo-dark,.custom-fields-component .fi-logo.fi-logo-light:where(.dark,.dark *){display:none}.custom-fields-component .fi-logo.fi-logo-dark:where(.dark,.dark *){display:flex}@media (min-width:48rem){.custom-fields-component .fi-page-sub-navigation-dropdown{display:none}}.custom-fields-component .fi-page-sub-navigation-dropdown>.fi-dropdown-trigger>.fi-btn{justify-content:space-between;width:100%}.custom-fields-component .fi-page-sub-navigation-sidebar-ctn{width:calc(var(--spacing)*72);flex-direction:column;display:none}@media (min-width:48rem){.custom-fields-component .fi-page-sub-navigation-sidebar-ctn{display:flex}}.custom-fields-component .fi-page-sub-navigation-sidebar{row-gap:calc(var(--spacing)*7);flex-direction:column;display:flex}.custom-fields-component .fi-page-sub-navigation-tabs{display:none}@media (min-width:48rem){.custom-fields-component .fi-page-sub-navigation-tabs{display:flex}}.custom-fields-component .fi-page.fi-height-full,.custom-fields-component .fi-page.fi-height-full .fi-page-content,.custom-fields-component .fi-page.fi-height-full .fi-page-header-main-ctn,.custom-fields-component .fi-page.fi-height-full .fi-page-main{height:100%}.custom-fields-component .fi-page.fi-page-has-sub-navigation .fi-page-main{gap:calc(var(--spacing)*8);flex-direction:column;display:flex}@media (min-width:48rem){.custom-fields-component :is(.fi-page.fi-page-has-sub-navigation.fi-page-has-sub-navigation-start,.fi-page.fi-page-has-sub-navigation.fi-page-has-sub-navigation-end) .fi-page-main{flex-direction:row;align-items:flex-start}}.custom-fields-component .fi-page-header-main-ctn{row-gap:calc(var(--spacing)*8);padding-block:calc(var(--spacing)*8);flex-direction:column;display:flex}.custom-fields-component .fi-page-main-sub-navigation-mobile-menu-render-hook-ctn{display:contents}@media (min-width:48rem){.custom-fields-component .fi-page-main-sub-navigation-mobile-menu-render-hook-ctn{display:none}}.custom-fields-component .fi-page-content{row-gap:calc(var(--spacing)*8);flex:1;grid-auto-columns:minmax(0,1fr);display:grid}.custom-fields-component .fi-simple-page-content{row-gap:calc(var(--spacing)*6);grid-auto-columns:minmax(0,1fr);display:grid}.custom-fields-component .fi-sidebar-group{row-gap:var(--spacing);flex-direction:column;display:flex}.custom-fields-component .fi-sidebar-group.fi-collapsed .fi-sidebar-group-collapse-btn{rotate:-180deg}.custom-fields-component .fi-sidebar-group.fi-collapsible>.fi-sidebar-group-btn{cursor:pointer}.custom-fields-component .fi-sidebar-group.fi-active .fi-sidebar-group-dropdown-trigger-btn .fi-icon{color:var(--primary-600)}.custom-fields-component .fi-sidebar-group.fi-active .fi-sidebar-group-dropdown-trigger-btn .fi-icon:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-sidebar-group-btn{align-items:center;column-gap:calc(var(--spacing)*3);padding:calc(var(--spacing)*2);display:flex}.custom-fields-component .fi-sidebar-group-btn .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-sidebar-group-btn .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-sidebar-group-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-500);flex:1}.custom-fields-component .fi-sidebar-group-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn{justify-content:center;align-items:center;column-gap:calc(var(--spacing)*3);border-radius:var(--radius-lg);padding:calc(var(--spacing)*2);--tw-outline-style:none;outline-style:none;flex:1;display:flex;position:relative}@media (forced-colors:active){.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:hover{background-color:var(--gray-100)}}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:focus-visible{background-color:var(--gray-100)}@media (hover:hover){.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn .fi-icon{color:var(--gray-400)}.custom-fields-component .fi-sidebar-group-dropdown-trigger-btn .fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-sidebar-group-items{row-gap:var(--spacing);flex-direction:column;display:flex}.custom-fields-component :is(.fi-sidebar-group-btn,.fi-sidebar-group-items).fi-transition-enter{transition-delay:.1s}@media (min-width:64rem){.custom-fields-component :is(.fi-sidebar-group-btn,.fi-sidebar-group-items).fi-transition-enter{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}}.custom-fields-component :is(.fi-sidebar-group-btn,.fi-sidebar-group-items).fi-transition-enter-start{opacity:0}.custom-fields-component :is(.fi-sidebar-group-btn,.fi-sidebar-group-items).fi-transition-enter-end{opacity:1}.custom-fields-component .fi-sidebar{z-index:30;background-color:var(--color-white);height:100dvh;transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));flex-direction:column;align-content:flex-start;display:flex;position:fixed;inset-block:0;inset-inline-start:calc(var(--spacing)*0)}@media (min-width:64rem){.custom-fields-component .fi-sidebar{z-index:20;background-color:#0000;transition-property:none}}.custom-fields-component .fi-sidebar:where(.dark,.dark *){background-color:var(--gray-900)}@media (min-width:64rem){.custom-fields-component .fi-sidebar:where(.dark,.dark *){background-color:#0000}}.custom-fields-component .fi-sidebar.fi-sidebar-open{width:var(--sidebar-width);--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar.fi-sidebar-open{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}@media (min-width:64rem){.custom-fields-component .fi-sidebar.fi-sidebar-open{--tw-shadow:0 0 #0000;--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.custom-fields-component .fi-sidebar.fi-sidebar-open:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-sidebar.fi-sidebar-open:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar.fi-sidebar-open:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-sidebar:not(.fi-sidebar-open){--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-sidebar:not(.fi-sidebar-open):where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}@media (min-width:64rem){.custom-fields-component .fi-body-has-topbar .fi-sidebar{top:var(--topbar-height);height:calc(100dvh - var(--topbar-height))}}.custom-fields-component .fi-sidebar-close-overlay{z-index:30;background-color:var(--gray-950);position:fixed;inset:0}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-close-overlay{background-color:color-mix(in oklab,var(--gray-950) 50%,transparent)}}.custom-fields-component .fi-sidebar-close-overlay{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.5s;transition-duration:.5s}@media (min-width:64rem){.custom-fields-component .fi-sidebar-close-overlay{display:none}}.custom-fields-component .fi-sidebar-close-overlay:where(.dark,.dark *){background-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-close-overlay:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-950) 75%,transparent)}}@media (min-width:64rem){.custom-fields-component .fi-body.fi-body-has-top-navigation .fi-sidebar{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-body.fi-body-has-top-navigation .fi-sidebar:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .fi-body:not(.fi-body-has-top-navigation) .fi-sidebar.fi-sidebar-open{position:sticky}.custom-fields-component .fi-body:not(.fi-body-has-top-navigation).fi-body-has-sidebar-collapsible-on-desktop .fi-sidebar:not(.fi-sidebar-open){--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y);position:sticky}.custom-fields-component .fi-body:not(.fi-body-has-top-navigation).fi-body-has-sidebar-collapsible-on-desktop .fi-sidebar:not(.fi-sidebar-open):where(:dir(rtl),[dir=rtl],[dir=rtl] *),.custom-fields-component .fi-body:not(.fi-body-has-top-navigation):not(.fi-body-has-sidebar-collapsible-on-desktop):not(.fi-body-has-sidebar-fully-collapsible-on-desktop) .fi-sidebar,.custom-fields-component .fi-body:not(.fi-body-has-top-navigation):not(.fi-body-has-sidebar-collapsible-on-desktop):not(.fi-body-has-sidebar-fully-collapsible-on-desktop) .fi-sidebar:where(:dir(rtl),[dir=rtl],[dir=rtl] *){--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}}.custom-fields-component .fi-body:not(.fi-body-has-top-navigation):not(.fi-body-has-sidebar-collapsible-on-desktop):not(.fi-body-has-sidebar-fully-collapsible-on-desktop) .fi-sidebar:not(.fi-sidebar-open){width:var(--sidebar-width)}@media (min-width:64rem){.custom-fields-component .fi-body:not(.fi-body-has-top-navigation):not(.fi-body-has-sidebar-collapsible-on-desktop):not(.fi-body-has-sidebar-fully-collapsible-on-desktop) .fi-sidebar:not(.fi-sidebar-open){position:sticky}}.custom-fields-component .fi-sidebar-header-ctn{overflow-x:clip}.custom-fields-component .fi-sidebar-header{height:var(--topbar-height);justify-content:center;align-items:center;display:flex}.custom-fields-component .fi-sidebar-header-logo-ctn{flex:1}.custom-fields-component .fi-body-has-topbar .fi-sidebar-header{background-color:var(--color-white);padding-inline:calc(var(--spacing)*6);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-body-has-topbar .fi-sidebar-header{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}@media (min-width:64rem){.custom-fields-component .fi-body-has-topbar .fi-sidebar-header{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);display:none}}.custom-fields-component .fi-body-has-topbar .fi-sidebar-header:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-body-has-topbar .fi-sidebar-header:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component :not(.fi-body-has-topbar) .fi-sidebar-header{padding-inline:calc(var(--spacing)*4);--tw-shadow:0 0 #0000;--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000}.custom-fields-component :not(.fi-body-has-topbar) .fi-sidebar-header .fi-logo{margin-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-sidebar-nav{row-gap:calc(var(--spacing)*7);padding-inline:calc(var(--spacing)*6);padding-block:calc(var(--spacing)*8);scrollbar-gutter:stable;flex-direction:column;flex-grow:1;display:flex;overflow:hidden auto}.custom-fields-component .fi-sidebar-nav-groups{margin-inline:calc(var(--spacing)*-2);row-gap:calc(var(--spacing)*7);flex-direction:column;display:flex}.custom-fields-component .fi-sidebar-item.fi-active,.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-active-child-items{row-gap:var(--spacing);flex-direction:column;display:flex}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn{background-color:var(--gray-100)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-icon{color:var(--primary-700)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-icon:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-sidebar-item-grouped-border>.fi-sidebar-item-grouped-border-part{background-color:var(--primary-700)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-sidebar-item-grouped-border>.fi-sidebar-item-grouped-border-part:where(.dark,.dark *){background-color:var(--primary-400)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-sidebar-item-label{color:var(--primary-700)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn>.fi-sidebar-item-label:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-sidebar-item.fi-active>.fi-sidebar-item-btn .fi-sidebar-item-grouped-border-part{height:calc(var(--spacing)*1.5);width:calc(var(--spacing)*1.5);border-radius:3.40282e+38px;position:relative}@media (hover:hover){.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:hover{background-color:var(--gray-100)}}.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:focus-visible{background-color:var(--gray-100)}@media (hover:hover){.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-item.fi-sidebar-item-has-url>.fi-sidebar-item-btn:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-sidebar-item-btn{justify-content:center;align-items:center;column-gap:calc(var(--spacing)*3);border-radius:var(--radius-lg);padding:calc(var(--spacing)*2);--tw-outline-style:none;outline-style:none;display:flex;position:relative}@media (forced-colors:active){.custom-fields-component .fi-sidebar-item-btn{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-sidebar-item-btn{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-sidebar-item-btn>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-sidebar-item-btn>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-sidebar-item-grouped-border{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6);justify-content:center;align-items:center;display:flex;position:relative}.custom-fields-component .fi-sidebar-item-grouped-border-part-not-first{background-color:var(--gray-300);width:1px;position:absolute;top:-50%;bottom:50%}.custom-fields-component .fi-sidebar-item-grouped-border-part-not-first:where(.dark,.dark *){background-color:var(--gray-600)}.custom-fields-component .fi-sidebar-item-grouped-border-part-not-last{background-color:var(--gray-300);width:1px;position:absolute;top:50%;bottom:-50%}.custom-fields-component .fi-sidebar-item-grouped-border-part-not-last:where(.dark,.dark *){background-color:var(--gray-600)}.custom-fields-component .fi-sidebar-item-grouped-border-part{height:calc(var(--spacing)*1.5);width:calc(var(--spacing)*1.5);background-color:var(--gray-400);border-radius:3.40282e+38px;position:relative}.custom-fields-component .fi-sidebar-item-grouped-border-part:where(.dark,.dark *){background-color:var(--gray-500)}.custom-fields-component .fi-sidebar-item-label{text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-700);flex:1;overflow:hidden}.custom-fields-component .fi-sidebar-item-label:where(.dark,.dark *){color:var(--gray-200)}@media (min-width:64rem){.custom-fields-component :is(.fi-sidebar-item-label,.fi-sidebar-item-badge-ctn).fi-transition-enter{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));transition-delay:.1s}}.custom-fields-component :is(.fi-sidebar-item-label,.fi-sidebar-item-badge-ctn).fi-transition-enter-start{opacity:0}.custom-fields-component :is(.fi-sidebar-item-label,.fi-sidebar-item-badge-ctn).fi-transition-enter-end{opacity:1}.custom-fields-component .fi-sidebar-footer{margin-inline:calc(var(--spacing)*4);margin-block:calc(var(--spacing)*3);row-gap:calc(var(--spacing)*3);display:grid}.custom-fields-component .fi-sidebar-footer>.fi-no-database{display:block}.custom-fields-component .fi-sidebar-sub-group-items{row-gap:var(--spacing);flex-direction:column;display:flex}.custom-fields-component .fi-sidebar-database-notifications-btn{justify-content:center;align-items:center;column-gap:calc(var(--spacing)*3);border-radius:var(--radius-lg);width:100%;padding:calc(var(--spacing)*2);text-align:start;--tw-outline-style:none;outline-style:none;display:flex;position:relative}@media (forced-colors:active){.custom-fields-component .fi-sidebar-database-notifications-btn{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-sidebar-database-notifications-btn{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-sidebar-database-notifications-btn:hover{background-color:var(--gray-100)}}.custom-fields-component .fi-sidebar-database-notifications-btn:focus-visible{background-color:var(--gray-100)}@media (hover:hover){.custom-fields-component .fi-sidebar-database-notifications-btn:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-database-notifications-btn:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-sidebar-database-notifications-btn:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-sidebar-database-notifications-btn:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-sidebar-database-notifications-btn>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-sidebar-database-notifications-btn>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-label{text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-700);flex:1;overflow:hidden}.custom-fields-component .fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-label:where(.dark,.dark *){color:var(--gray-200)}@media (min-width:64rem){.custom-fields-component :is(.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-label,.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-badge-ctn).fi-transition-enter{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));transition-delay:.1s}}.custom-fields-component :is(.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-label,.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-badge-ctn).fi-transition-enter-start{opacity:0}.custom-fields-component :is(.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-label,.fi-sidebar-database-notifications-btn>.fi-sidebar-database-notifications-btn-badge-ctn).fi-transition-enter-end{opacity:1}.custom-fields-component .fi-sidebar-open-collapse-sidebar-btn,.custom-fields-component .fi-sidebar-open-sidebar-btn{margin-inline:0!important}.custom-fields-component .fi-sidebar-close-collapse-sidebar-btn{display:none;margin-inline:0!important}@media (min-width:64rem){.custom-fields-component .fi-sidebar-close-collapse-sidebar-btn{display:flex}.custom-fields-component :is(.fi-body.fi-body-has-sidebar-collapsible-on-desktop,.fi-body:not(.fi-body-has-sidebar-fully-collapsible-on-desktop)) .fi-sidebar-open-sidebar-btn{display:none}}.custom-fields-component .fi-sidebar-close-sidebar-btn{margin-inline:0!important}@media (min-width:64rem){.custom-fields-component .fi-sidebar-close-sidebar-btn{display:none}}.custom-fields-component .fi-tenant-menu-trigger{justify-content:center;align-items:center;column-gap:calc(var(--spacing)*3);border-radius:var(--radius-lg);width:100%;padding:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-outline-style:none;outline-style:none;display:flex}@media (forced-colors:active){.custom-fields-component .fi-tenant-menu-trigger{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-tenant-menu-trigger{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-tenant-menu-trigger:hover{background-color:var(--gray-100)}}.custom-fields-component .fi-tenant-menu-trigger:focus-visible{background-color:var(--gray-100)}@media (hover:hover){.custom-fields-component .fi-tenant-menu-trigger:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tenant-menu-trigger:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-tenant-menu-trigger:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-tenant-menu-trigger:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-tenant-menu-trigger .fi-tenant-avatar{flex-shrink:0}.custom-fields-component .fi-tenant-menu-trigger .fi-icon{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);color:var(--gray-400);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;flex-shrink:0;margin-inline-start:auto;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-tenant-menu-trigger .fi-icon:is(:where(.group):hover *){color:var(--gray-500)}}.custom-fields-component .fi-tenant-menu-trigger .fi-icon:is(:where(.group):focus-visible *),.custom-fields-component .fi-tenant-menu-trigger .fi-icon:where(.dark,.dark *){color:var(--gray-500)}@media (hover:hover){.custom-fields-component .fi-tenant-menu-trigger .fi-icon:where(.dark,.dark *):is(:where(.group):hover *){color:var(--gray-400)}}.custom-fields-component .fi-tenant-menu-trigger .fi-icon:where(.dark,.dark *):is(:where(.group):focus-visible *){color:var(--gray-400)}.custom-fields-component .fi-tenant-menu-trigger:hover .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-tenant-menu-trigger:hover .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-tenant-menu-trigger:focus-visible .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-tenant-menu-trigger:focus-visible .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-tenant-menu-trigger-text{text-align:start;justify-items:start;display:grid}.custom-fields-component .fi-tenant-menu-trigger-current-tenant-label{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--gray-500)}.custom-fields-component .fi-tenant-menu-trigger-current-tenant-label:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-tenant-menu-trigger-tenant-name{color:var(--gray-950)}.custom-fields-component .fi-tenant-menu-trigger-tenant-name:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sidebar .fi-tenant-menu{margin-inline:calc(var(--spacing)*4);margin-top:calc(var(--spacing)*3)}.custom-fields-component .fi-theme-switcher{column-gap:var(--spacing);grid-auto-flow:column;display:grid}.custom-fields-component .fi-theme-switcher-btn{border-radius:var(--radius-md);padding:calc(var(--spacing)*2);--tw-outline-style:none;outline-style:none;justify-content:center;display:flex}@media (forced-colors:active){.custom-fields-component .fi-theme-switcher-btn{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-theme-switcher-btn{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-theme-switcher-btn:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-theme-switcher-btn:focus-visible{background-color:var(--gray-50);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600)}@media (hover:hover){.custom-fields-component .fi-theme-switcher-btn:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-theme-switcher-btn:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-theme-switcher-btn:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-theme-switcher-btn:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-theme-switcher-btn:where(.dark,.dark *):focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .fi-theme-switcher-btn.fi-active{background-color:var(--gray-50);color:var(--primary-500)}.custom-fields-component .fi-theme-switcher-btn.fi-active:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-theme-switcher-btn.fi-active:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-theme-switcher-btn.fi-active:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-theme-switcher-btn:not(.fi-active){color:var(--gray-400)}@media (hover:hover){.custom-fields-component .fi-theme-switcher-btn:not(.fi-active):hover{color:var(--gray-500)}}.custom-fields-component .fi-theme-switcher-btn:not(.fi-active):focus-visible,.custom-fields-component .fi-theme-switcher-btn:not(.fi-active):where(.dark,.dark *){color:var(--gray-500)}@media (hover:hover){.custom-fields-component .fi-theme-switcher-btn:not(.fi-active):where(.dark,.dark *):hover{color:var(--gray-400)}}.custom-fields-component .fi-theme-switcher-btn:not(.fi-active):where(.dark,.dark *):focus-visible{color:var(--gray-400)}.custom-fields-component .fi-body{--topbar-height:4rem}.custom-fields-component .fi-topbar-ctn{z-index:30;position:sticky;top:0;overflow-x:clip}.custom-fields-component .fi-topbar{min-height:var(--topbar-height);background-color:var(--color-white);padding-inline:calc(var(--spacing)*4);--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--gray-950);align-items:center;display:flex}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-topbar{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .fi-topbar:where(.dark,.dark *){background-color:var(--gray-900);--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-topbar:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-topbar .fi-tenant-menu{display:none}@media (min-width:64rem){.custom-fields-component .fi-topbar .fi-tenant-menu{display:block}}.custom-fields-component .fi-topbar-close-sidebar-btn,.custom-fields-component .fi-topbar-open-sidebar-btn{margin-inline:0!important}@media (min-width:64rem){.custom-fields-component .fi-topbar-close-sidebar-btn{display:none}}.custom-fields-component .fi-topbar-open-collapse-sidebar-btn{margin-inline:0!important}.custom-fields-component .fi-topbar-close-collapse-sidebar-btn{display:none;margin-inline:0!important}@media (min-width:64rem){.custom-fields-component .fi-topbar-close-collapse-sidebar-btn{display:flex}}.custom-fields-component .fi-topbar-start{align-items:center;margin-inline-end:calc(var(--spacing)*6);display:none}@media (min-width:64rem){.custom-fields-component .fi-topbar-start{display:flex}}.custom-fields-component .fi-topbar-start .fi-logo{margin-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-topbar-collapse-sidebar-btn-ctn{width:calc(var(--spacing)*9);flex-shrink:0}@media (min-width:64rem){.custom-fields-component :is(.fi-body.fi-body-has-sidebar-collapsible-on-desktop,.fi-body:not(.fi-body-has-sidebar-fully-collapsible-on-desktop)) .fi-topbar-open-sidebar-btn{display:none}}.custom-fields-component .fi-topbar-nav-groups{align-items:center;column-gap:calc(var(--spacing)*4);margin-inline-start:calc(var(--spacing)*4);margin-inline-end:calc(var(--spacing)*4);display:none}@media (min-width:64rem){.custom-fields-component .fi-topbar-nav-groups{margin-block:calc(var(--spacing)*2);row-gap:var(--spacing);flex-wrap:wrap;display:flex}}.custom-fields-component .fi-topbar-end{align-items:center;column-gap:calc(var(--spacing)*4);margin-inline-start:auto;display:flex}.custom-fields-component .fi-topbar-item-btn{justify-content:center;align-items:center;column-gap:calc(var(--spacing)*2);border-radius:var(--radius-lg);padding-inline:calc(var(--spacing)*3);padding-block:calc(var(--spacing)*2);--tw-outline-style:none;outline-style:none;display:flex}@media (forced-colors:active){.custom-fields-component .fi-topbar-item-btn{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-topbar-item-btn{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-topbar-item-btn:hover{background-color:var(--gray-50)}}.custom-fields-component .fi-topbar-item-btn:focus-visible{background-color:var(--gray-50)}@media (hover:hover){.custom-fields-component .fi-topbar-item-btn:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-topbar-item-btn:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}}.custom-fields-component .fi-topbar-item-btn:where(.dark,.dark *):focus-visible{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-topbar-item-btn:where(.dark,.dark *):focus-visible{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-topbar-item-btn>.fi-icon{color:var(--gray-400)}.custom-fields-component .fi-topbar-item-btn>.fi-icon:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .fi-topbar-item-label{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-700)}.custom-fields-component .fi-topbar-item-label:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-btn{background-color:var(--gray-50)}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-btn:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-btn:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-btn>.fi-icon{color:var(--primary-600)}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-btn>.fi-icon:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-label{color:var(--primary-600)}.custom-fields-component .fi-topbar-item.fi-active .fi-topbar-item-label:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .fi-simple-user-menu-ctn{align-items:center;column-gap:calc(var(--spacing)*4);display:flex}.custom-fields-component .fi-topbar .fi-user-menu-trigger{flex-shrink:0}.custom-fields-component .fi-sidebar .fi-user-menu-trigger{justify-content:center;align-items:center;column-gap:calc(var(--spacing)*3);border-radius:var(--radius-lg);width:100%;padding:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);--tw-outline-style:none;outline-style:none;display:flex}@media (forced-colors:active){.custom-fields-component .fi-sidebar .fi-user-menu-trigger{outline-offset:2px;outline:2px solid #0000}}.custom-fields-component .fi-sidebar .fi-user-menu-trigger{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-user-avatar{flex-shrink:0}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5);color:var(--gray-400);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:75ms;flex-shrink:0;margin-inline-start:auto;transition-duration:75ms}@media (hover:hover){.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon:is(:where(.group):hover *){color:var(--gray-500)}}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon:is(:where(.group):focus-visible *),.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon:where(.dark,.dark *){color:var(--gray-500)}@media (hover:hover){.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon:where(.dark,.dark *):is(:where(.group):hover *){color:var(--gray-400)}}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-icon:where(.dark,.dark *):is(:where(.group):focus-visible *){color:var(--gray-400)}.custom-fields-component .fi-sidebar .fi-user-menu-trigger:hover .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-sidebar .fi-user-menu-trigger:hover .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sidebar .fi-user-menu-trigger:focus-visible .fi-icon{color:var(--gray-500)}.custom-fields-component .fi-sidebar .fi-user-menu-trigger:focus-visible .fi-icon:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-tenant-menu-trigger-text{text-align:start;color:var(--gray-950);justify-items:start;display:grid}.custom-fields-component .fi-sidebar .fi-user-menu-trigger .fi-tenant-menu-trigger-text:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-sidebar .fi-user-menu .fi-dropdown-panel{max-width:max(14rem,100% - 1.5rem)!important}.custom-fields-component .fi-account-widget .fi-section-content{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-account-widget-logout-form{margin-block:auto}.custom-fields-component .fi-account-widget-main{flex:1}.custom-fields-component .fi-account-widget-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--gray-950);flex:1;display:grid}.custom-fields-component .fi-account-widget-heading:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-account-widget-user-name{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--gray-500)}.custom-fields-component .fi-account-widget-user-name:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-filament-info-widget .fi-section-content{align-items:center;column-gap:calc(var(--spacing)*3);display:flex}.custom-fields-component .fi-filament-info-widget-main{flex:1}.custom-fields-component .fi-filament-info-widget-logo{height:calc(var(--spacing)*5);color:var(--gray-950)}.custom-fields-component .fi-filament-info-widget-logo:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .fi-filament-info-widget-version{margin-top:calc(var(--spacing)*2);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--gray-500)}.custom-fields-component .fi-filament-info-widget-version:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .fi-filament-info-widget-links{align-items:flex-end;row-gap:var(--spacing);flex-direction:column;display:flex}}@layer utilities{.custom-fields-component .pointer-events-none{pointer-events:none}.custom-fields-component .invisible{visibility:hidden}.custom-fields-component .visible{visibility:visible}.custom-fields-component .sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.custom-fields-component .absolute{position:absolute}.custom-fields-component .fixed{position:fixed}.custom-fields-component .relative{position:relative}.custom-fields-component .static{position:static}.custom-fields-component .top-1\/2{top:50%}.custom-fields-component .right-0{right:0}.custom-fields-component .left-3{left:calc(var(--spacing)*3)}.custom-fields-component .z-50{z-index:50}.custom-fields-component .container{width:100%}@media (min-width:40rem){.custom-fields-component .container{max-width:40rem}}@media (min-width:48rem){.custom-fields-component .container{max-width:48rem}}@media (min-width:64rem){.custom-fields-component .container{max-width:64rem}}@media (min-width:80rem){.custom-fields-component .container{max-width:80rem}}@media (min-width:96rem){.custom-fields-component .container{max-width:96rem}}.custom-fields-component .-mx-1{margin-inline:calc(var(--spacing)*-1)}.custom-fields-component .mx-auto{margin-inline:auto}.custom-fields-component .mt-0\.5{margin-top:calc(var(--spacing)*.5)}.custom-fields-component .mt-1{margin-top:var(--spacing)}.custom-fields-component .mt-2{margin-top:calc(var(--spacing)*2)}.custom-fields-component .mt-3{margin-top:calc(var(--spacing)*3)}.custom-fields-component .mt-6{margin-top:calc(var(--spacing)*6)}.custom-fields-component .mb-1{margin-bottom:var(--spacing)}.custom-fields-component .mb-2{margin-bottom:calc(var(--spacing)*2)}.custom-fields-component .mb-4{margin-bottom:calc(var(--spacing)*4)}.custom-fields-component .mb-6{margin-bottom:calc(var(--spacing)*6)}.custom-fields-component .ml-0\.5{margin-left:calc(var(--spacing)*.5)}.custom-fields-component .ml-2{margin-left:calc(var(--spacing)*2)}.custom-fields-component .ml-auto{margin-left:auto}.custom-fields-component .block{display:block}.custom-fields-component .flex{display:flex}.custom-fields-component .grid{display:grid}.custom-fields-component .hidden{display:none}.custom-fields-component .inline{display:inline}.custom-fields-component .inline-flex{display:inline-flex}.custom-fields-component .table{display:table}.custom-fields-component .size-3{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.custom-fields-component .size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.custom-fields-component .size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.custom-fields-component .h-2\.5{height:calc(var(--spacing)*2.5)}.custom-fields-component .h-3{height:calc(var(--spacing)*3)}.custom-fields-component .h-3\.5{height:calc(var(--spacing)*3.5)}.custom-fields-component .h-4{height:calc(var(--spacing)*4)}.custom-fields-component .h-4\.5{height:calc(var(--spacing)*4.5)}.custom-fields-component .h-5{height:calc(var(--spacing)*5)}.custom-fields-component .h-6{height:calc(var(--spacing)*6)}.custom-fields-component .h-8{height:calc(var(--spacing)*8)}.custom-fields-component .h-full{height:100%}.custom-fields-component .max-h-48{max-height:calc(var(--spacing)*48)}.custom-fields-component .max-h-60{max-height:calc(var(--spacing)*60)}.custom-fields-component .max-h-\[22rem\]{max-height:22rem}.custom-fields-component .max-h-\[280px\]{max-height:280px}.custom-fields-component .min-h-\[2\.25rem\]{min-height:2.25rem}.custom-fields-component .min-h-\[3\.25rem\]{min-height:3.25rem}.custom-fields-component .min-h-\[28px\]{min-height:28px}.custom-fields-component .min-h-\[50px\]{min-height:50px}.custom-fields-component .w-1\/3{width:33.3333%}.custom-fields-component .w-2\.5{width:calc(var(--spacing)*2.5)}.custom-fields-component .w-3{width:calc(var(--spacing)*3)}.custom-fields-component .w-3\.5{width:calc(var(--spacing)*3.5)}.custom-fields-component .w-4{width:calc(var(--spacing)*4)}.custom-fields-component .w-4\.5{width:calc(var(--spacing)*4.5)}.custom-fields-component .w-5{width:calc(var(--spacing)*5)}.custom-fields-component .w-6{width:calc(var(--spacing)*6)}.custom-fields-component .w-8{width:calc(var(--spacing)*8)}.custom-fields-component .w-20{width:calc(var(--spacing)*20)}.custom-fields-component .w-23{width:calc(var(--spacing)*23)}.custom-fields-component .w-24{width:calc(var(--spacing)*24)}.custom-fields-component .w-64{width:calc(var(--spacing)*64)}.custom-fields-component .w-\[180px\]{width:180px}.custom-fields-component .w-\[220px\]{width:220px}.custom-fields-component .w-\[240px\]{width:240px}.custom-fields-component .w-\[260px\]{width:260px}.custom-fields-component .w-full{width:100%}.custom-fields-component .w-px{width:1px}.custom-fields-component .max-w-\[10rem\]{max-width:10rem}.custom-fields-component .max-w-\[100px\]{max-width:100px}.custom-fields-component .max-w-\[120px\]{max-width:120px}.custom-fields-component .max-w-\[200px\]{max-width:200px}.custom-fields-component .max-w-\[250px\]{max-width:250px}.custom-fields-component .max-w-full{max-width:100%}.custom-fields-component .max-w-md{max-width:var(--container-md)}.custom-fields-component .max-w-sm{max-width:var(--container-sm)}.custom-fields-component .max-w-xs{max-width:var(--container-xs)}.custom-fields-component .min-w-0{min-width:0}.custom-fields-component .min-w-\[600px\]{min-width:600px}.custom-fields-component .min-w-\[640px\]{min-width:640px}.custom-fields-component .flex-1{flex:1}.custom-fields-component .shrink-0{flex-shrink:0}.custom-fields-component .-translate-y-1\/2{--tw-translate-y:-50%;translate:var(--tw-translate-x) var(--tw-translate-y)}.custom-fields-component .rotate-180{rotate:180deg}.custom-fields-component .transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.custom-fields-component .animate-pulse{animation:var(--animate-pulse)}.custom-fields-component .cursor-grab{cursor:grab}.custom-fields-component .cursor-pointer{cursor:pointer}.custom-fields-component .grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.custom-fields-component .grid-cols-\[40px_1fr\]{grid-template-columns:40px 1fr}.custom-fields-component .grid-cols-\[40px_1fr_minmax\(120px\,160px\)_minmax\(100px\,140px\)_minmax\(80px\,120px\)_50px\]{grid-template-columns:40px 1fr minmax(120px,160px) minmax(100px,140px) minmax(80px,120px) 50px}.custom-fields-component .grid-cols-\[40px_minmax\(0\,1fr\)\]{grid-template-columns:40px minmax(0,1fr)}.custom-fields-component .grid-cols-\[40px_minmax\(0\,1fr\)_minmax\(120px\,180px\)_minmax\(120px\,1fr\)_50px\]{grid-template-columns:40px minmax(0,1fr) minmax(120px,180px) minmax(120px,1fr) 50px}.custom-fields-component .flex-col{flex-direction:column}.custom-fields-component .flex-wrap{flex-wrap:wrap}.custom-fields-component .items-center{align-items:center}.custom-fields-component .items-start{align-items:flex-start}.custom-fields-component .justify-between{justify-content:space-between}.custom-fields-component .justify-center{justify-content:center}.custom-fields-component .justify-items-center{justify-items:center}.custom-fields-component .gap-1{gap:var(--spacing)}.custom-fields-component .gap-1\.5{gap:calc(var(--spacing)*1.5)}.custom-fields-component .gap-2{gap:calc(var(--spacing)*2)}.custom-fields-component .gap-3{gap:calc(var(--spacing)*3)}.custom-fields-component .gap-4{gap:calc(var(--spacing)*4)}.custom-fields-component .gap-6{gap:calc(var(--spacing)*6)}.custom-fields-component :where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing)*4*var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing)*4*(1 - var(--tw-space-y-reverse)))}.custom-fields-component .gap-x-1{column-gap:var(--spacing)}.custom-fields-component .gap-x-2{column-gap:calc(var(--spacing)*2)}.custom-fields-component .gap-x-3{column-gap:calc(var(--spacing)*3)}.custom-fields-component .gap-x-4{column-gap:calc(var(--spacing)*4)}.custom-fields-component .gap-y-1{row-gap:var(--spacing)}.custom-fields-component .gap-y-2{row-gap:calc(var(--spacing)*2)}.custom-fields-component .gap-y-6{row-gap:calc(var(--spacing)*6)}.custom-fields-component :where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.custom-fields-component :where(.divide-gray-100>:not(:last-child)){border-color:var(--gray-100)}.custom-fields-component :where(.divide-gray-200>:not(:last-child)){border-color:var(--gray-200)}.custom-fields-component .truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.custom-fields-component .overflow-hidden{overflow:hidden}.custom-fields-component .overflow-x-auto{overflow-x:auto}.custom-fields-component .overflow-y-auto{overflow-y:auto}.custom-fields-component .rounded{border-radius:.25rem}.custom-fields-component .rounded-full{border-radius:3.40282e+38px}.custom-fields-component .rounded-lg{border-radius:var(--radius-lg)}.custom-fields-component .rounded-md{border-radius:var(--radius-md)}.custom-fields-component .rounded-xl{border-radius:var(--radius-xl)}.custom-fields-component .rounded-s-md{border-start-start-radius:var(--radius-md);border-end-start-radius:var(--radius-md)}.custom-fields-component .rounded-e-md{border-start-end-radius:var(--radius-md);border-end-end-radius:var(--radius-md)}.custom-fields-component .rounded-t-lg{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.custom-fields-component .rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.custom-fields-component .rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.custom-fields-component .border{border-style:var(--tw-border-style);border-width:1px}.custom-fields-component .border-0{border-style:var(--tw-border-style);border-width:0}.custom-fields-component .border-s{border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.custom-fields-component .border-s-2{border-inline-start-style:var(--tw-border-style);border-inline-start-width:2px}.custom-fields-component .border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.custom-fields-component .border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.custom-fields-component .border-dashed{--tw-border-style:dashed;border-style:dashed}.custom-fields-component .border-none{--tw-border-style:none;border-style:none}.custom-fields-component .border-gray-100{border-color:var(--gray-100)}.custom-fields-component .border-gray-200{border-color:var(--gray-200)}.custom-fields-component .border-gray-300{border-color:var(--gray-300)}.custom-fields-component .border-primary-600{border-color:var(--primary-600)}.custom-fields-component .border-warning-200{border-color:var(--warning-200)}.custom-fields-component .border-s-primary-500{border-inline-start-color:var(--primary-500)}.custom-fields-component .bg-danger-50{background-color:var(--danger-50)}.custom-fields-component .bg-gray-50{background-color:var(--gray-50)}.custom-fields-component .bg-gray-100{background-color:var(--gray-100)}.custom-fields-component .bg-gray-200{background-color:var(--gray-200)}.custom-fields-component .bg-info-50{background-color:var(--info-50)}.custom-fields-component .bg-primary-50{background-color:var(--primary-50)}.custom-fields-component .bg-primary-600{background-color:var(--primary-600)}.custom-fields-component .bg-transparent{background-color:#0000}.custom-fields-component .bg-warning-50{background-color:var(--warning-50)}.custom-fields-component .bg-warning-600{background-color:var(--warning-600)}.custom-fields-component .bg-white{background-color:var(--color-white)}.custom-fields-component .bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.custom-fields-component .from-gray-100\/90{--tw-gradient-from:var(--gray-100)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .from-gray-100\/90{--tw-gradient-from:color-mix(in oklab, var(--gray-100) 90%, transparent)}}.custom-fields-component .from-gray-100\/90{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.custom-fields-component .via-gray-100\/100{--tw-gradient-via:var(--gray-100);--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.custom-fields-component .to-gray-100{--tw-gradient-to:var(--gray-100);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.custom-fields-component .object-cover{object-fit:cover}.custom-fields-component .p-0{padding:0}.custom-fields-component .p-0\.5{padding:calc(var(--spacing)*.5)}.custom-fields-component .p-1{padding:var(--spacing)}.custom-fields-component .p-1\.5{padding:calc(var(--spacing)*1.5)}.custom-fields-component .p-3{padding:calc(var(--spacing)*3)}.custom-fields-component .p-4{padding:calc(var(--spacing)*4)}.custom-fields-component .\!px-2{padding-inline:calc(var(--spacing)*2)!important}.custom-fields-component .px-1{padding-inline:var(--spacing)}.custom-fields-component .px-2{padding-inline:calc(var(--spacing)*2)}.custom-fields-component .px-3{padding-inline:calc(var(--spacing)*3)}.custom-fields-component .px-4{padding-inline:calc(var(--spacing)*4)}.custom-fields-component .px-6{padding-inline:calc(var(--spacing)*6)}.custom-fields-component .\!py-2{padding-block:calc(var(--spacing)*2)!important}.custom-fields-component .py-0\.5{padding-block:calc(var(--spacing)*.5)}.custom-fields-component .py-1{padding-block:var(--spacing)}.custom-fields-component .py-1\.5{padding-block:calc(var(--spacing)*1.5)}.custom-fields-component .py-2{padding-block:calc(var(--spacing)*2)}.custom-fields-component .py-2\.5{padding-block:calc(var(--spacing)*2.5)}.custom-fields-component .py-3{padding-block:calc(var(--spacing)*3)}.custom-fields-component .py-4{padding-block:calc(var(--spacing)*4)}.custom-fields-component .py-6{padding-block:calc(var(--spacing)*6)}.custom-fields-component .py-12{padding-block:calc(var(--spacing)*12)}.custom-fields-component .py-16{padding-block:calc(var(--spacing)*16)}.custom-fields-component .ps-1{padding-inline-start:var(--spacing)}.custom-fields-component .pe-1\.5{padding-inline-end:calc(var(--spacing)*1.5)}.custom-fields-component .pe-2{padding-inline-end:calc(var(--spacing)*2)}.custom-fields-component .pt-4{padding-top:calc(var(--spacing)*4)}.custom-fields-component .pr-1{padding-right:var(--spacing)}.custom-fields-component .pr-1\.5{padding-right:calc(var(--spacing)*1.5)}.custom-fields-component .pr-3{padding-right:calc(var(--spacing)*3)}.custom-fields-component .pl-2{padding-left:calc(var(--spacing)*2)}.custom-fields-component .pl-3{padding-left:calc(var(--spacing)*3)}.custom-fields-component .pl-9{padding-left:calc(var(--spacing)*9)}.custom-fields-component .text-center{text-align:center}.custom-fields-component .text-left{text-align:left}.custom-fields-component .text-start{text-align:start}.custom-fields-component .text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.custom-fields-component .text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.custom-fields-component .text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.custom-fields-component .text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.custom-fields-component .text-\[0\.625rem\]{font-size:.625rem}.custom-fields-component .leading-5{--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5)}.custom-fields-component .leading-7{--tw-leading:calc(var(--spacing)*7);line-height:calc(var(--spacing)*7)}.custom-fields-component .leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.custom-fields-component .font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.custom-fields-component .font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.custom-fields-component .tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.custom-fields-component .whitespace-nowrap{white-space:nowrap}.custom-fields-component .text-danger-700{color:var(--danger-700)}.custom-fields-component .text-gray-400{color:var(--gray-400)}.custom-fields-component .text-gray-500{color:var(--gray-500)}.custom-fields-component .text-gray-600{color:var(--gray-600)}.custom-fields-component .text-gray-700{color:var(--gray-700)}.custom-fields-component .text-gray-900{color:var(--gray-900)}.custom-fields-component .text-gray-950{color:var(--gray-950)}.custom-fields-component .text-green-500{color:var(--color-green-500)}.custom-fields-component .text-info-700{color:var(--info-700)}.custom-fields-component .text-neutral-700{color:var(--color-neutral-700)}.custom-fields-component .text-primary-500{color:var(--primary-500)}.custom-fields-component .text-primary-600{color:var(--primary-600)}.custom-fields-component .text-primary-700{color:var(--primary-700)}.custom-fields-component .text-warning-700{color:var(--warning-700)}.custom-fields-component .text-warning-800{color:var(--warning-800)}.custom-fields-component .text-white{color:var(--color-white)}.custom-fields-component .lowercase{text-transform:lowercase}.custom-fields-component .uppercase{text-transform:uppercase}.custom-fields-component .underline{text-decoration-line:underline}.custom-fields-component .decoration-gray-300{-webkit-text-decoration-color:var(--gray-300);text-decoration-color:var(--gray-300)}.custom-fields-component .decoration-1{text-decoration-thickness:1px}.custom-fields-component .underline-offset-2{text-underline-offset:2px}.custom-fields-component .opacity-0{opacity:0}.custom-fields-component .opacity-60{opacity:.6}.custom-fields-component .opacity-70{opacity:.7}.custom-fields-component .shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.custom-fields-component .shadow,.custom-fields-component .shadow-lg{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a)}.custom-fields-component .shadow-none{--tw-shadow:0 0 #0000}.custom-fields-component .shadow-none,.custom-fields-component .shadow-sm{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a)}.custom-fields-component .ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)}.custom-fields-component .ring-1,.custom-fields-component .ring-2{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .ring-2{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)}.custom-fields-component .ring-gray-950\/5{--tw-ring-color:var(--gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .ring-gray-950\/5{--tw-ring-color:color-mix(in oklab, var(--gray-950) 5%, transparent)}}.custom-fields-component .ring-primary-600,.custom-fields-component .ring-primary-600\/10{--tw-ring-color:var(--primary-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .ring-primary-600\/10{--tw-ring-color:color-mix(in oklab, var(--primary-600) 10%, transparent)}}.custom-fields-component .ring-warning-600\/20{--tw-ring-color:var(--warning-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .ring-warning-600\/20{--tw-ring-color:color-mix(in oklab, var(--warning-600) 20%, transparent)}}.custom-fields-component .filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.custom-fields-component .transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events}.custom-fields-component .transition,.custom-fields-component .transition-all{transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.custom-fields-component .transition-all{transition-property:all}.custom-fields-component .transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to}.custom-fields-component .transition-colors,.custom-fields-component .transition-opacity{transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.custom-fields-component .transition-opacity{transition-property:opacity}.custom-fields-component .transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.custom-fields-component .duration-75{--tw-duration:75ms;transition-duration:75ms}.custom-fields-component .duration-200{--tw-duration:.2s;transition-duration:.2s}.custom-fields-component .duration-300{--tw-duration:.3s;transition-duration:.3s}.custom-fields-component .outline-none{--tw-outline-style:none;outline-style:none}@media (hover:hover){.custom-fields-component .group-hover\/item\:opacity-100:is(:where(.group\/item):hover *),.custom-fields-component .group-hover\/value\:opacity-100:is(:where(.group\/value):hover *),.custom-fields-component .group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.custom-fields-component .placeholder\:text-gray-400::placeholder{color:var(--gray-400)}.custom-fields-component .first\:rounded-t-lg:first-child{border-top-left-radius:var(--radius-lg);border-top-right-radius:var(--radius-lg)}.custom-fields-component .last\:rounded-b-lg:last-child{border-bottom-right-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.custom-fields-component .last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.custom-fields-component .hover\:border-gray-300:hover{border-color:var(--gray-300)}.custom-fields-component .hover\:border-gray-400:hover{border-color:var(--gray-400)}.custom-fields-component .hover\:bg-danger-50:hover{background-color:var(--danger-50)}.custom-fields-component .hover\:bg-gray-50:hover{background-color:var(--gray-50)}.custom-fields-component .hover\:bg-gray-100:hover{background-color:var(--gray-100)}.custom-fields-component .hover\:bg-gray-200:hover{background-color:var(--gray-200)}.custom-fields-component .hover\:bg-gray-300:hover{background-color:var(--gray-300)}.custom-fields-component .hover\:bg-primary-50:hover{background-color:var(--primary-50)}.custom-fields-component .hover\:bg-primary-600\/80:hover{background-color:var(--primary-600)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .hover\:bg-primary-600\/80:hover{background-color:color-mix(in oklab,var(--primary-600) 80%,transparent)}}.custom-fields-component .hover\:bg-warning-100:hover{background-color:var(--warning-100)}.custom-fields-component .hover\:bg-warning-500:hover{background-color:var(--warning-500)}.custom-fields-component .hover\:text-danger-500:hover{color:var(--danger-500)}.custom-fields-component .hover\:text-gray-500:hover{color:var(--gray-500)}.custom-fields-component .hover\:text-gray-600:hover{color:var(--gray-600)}.custom-fields-component .hover\:text-primary-600:hover{color:var(--primary-600)}}.custom-fields-component .focus\:opacity-100:focus{opacity:1}.custom-fields-component .focus\:ring-0:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)}.custom-fields-component .focus\:ring-0:focus,.custom-fields-component .focus\:ring-2:focus{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor)}.custom-fields-component .focus\:ring-primary-500:focus{--tw-ring-color:var(--primary-500)}.custom-fields-component .focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.custom-fields-component .focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.custom-fields-component .focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.custom-fields-component .focus-visible\:ring-primary-500:focus-visible{--tw-ring-color:var(--primary-500)}.custom-fields-component .focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.custom-fields-component .active\:cursor-grabbing:active{cursor:grabbing}.custom-fields-component .disabled\:pointer-events-none:disabled{pointer-events:none}.custom-fields-component .disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.custom-fields-component .disabled\:text-gray-500:disabled{color:var(--gray-500)}.custom-fields-component .disabled\:opacity-40:disabled{opacity:.4}.custom-fields-component .disabled\:opacity-50:disabled{opacity:.5}.custom-fields-component .disabled\:opacity-60:disabled{opacity:.6}.custom-fields-component .disabled\:opacity-70:disabled{opacity:.7}@media (hover:hover){.custom-fields-component .disabled\:hover\:bg-transparent:disabled:hover{background-color:#0000}}@media (min-width:40rem){.custom-fields-component .sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.custom-fields-component .sm\:flex-row{flex-direction:row}.custom-fields-component .sm\:items-center{align-items:center}.custom-fields-component .sm\:justify-between{justify-content:space-between}}@media (min-width:48rem){.custom-fields-component .md\:block{display:block}.custom-fields-component .md\:hidden{display:none}.custom-fields-component .md\:min-w-48{min-width:calc(var(--spacing)*48)}.custom-fields-component .md\:grid-cols-\[minmax\(0\,1fr\)_minmax\(0\,14rem\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1fr) minmax(0,14rem) minmax(0,1fr)}.custom-fields-component .md\:flex-row{flex-direction:row}.custom-fields-component .md\:items-start{align-items:flex-start}}@media (min-width:64rem){.custom-fields-component .lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}.custom-fields-component :where(.dark\:divide-white\/5:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.dark\:divide-white\/5:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component :where(.dark\:divide-white\/10:where(.dark,.dark *)>:not(:last-child)){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component :where(.dark\:divide-white\/10:where(.dark,.dark *)>:not(:last-child)){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .dark\:border-gray-600:where(.dark,.dark *){border-color:var(--gray-600)}.custom-fields-component .dark\:border-gray-800:where(.dark,.dark *){border-color:var(--gray-800)}.custom-fields-component .dark\:border-primary-400:where(.dark,.dark *){border-color:var(--primary-400)}.custom-fields-component .dark\:border-primary-500:where(.dark,.dark *){border-color:var(--primary-500)}.custom-fields-component .dark\:border-warning-400\/20:where(.dark,.dark *){border-color:var(--warning-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:border-warning-400\/20:where(.dark,.dark *){border-color:color-mix(in oklab,var(--warning-400) 20%,transparent)}}.custom-fields-component .dark\:border-white\/10:where(.dark,.dark *){border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:border-white\/10:where(.dark,.dark *){border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .dark\:border-s-primary-400:where(.dark,.dark *){border-inline-start-color:var(--primary-400)}.custom-fields-component .dark\:bg-danger-400\/10:where(.dark,.dark *){background-color:var(--danger-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-danger-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--danger-400) 10%,transparent)}}.custom-fields-component .dark\:bg-gray-700:where(.dark,.dark *){background-color:var(--gray-700)}.custom-fields-component .dark\:bg-gray-800:where(.dark,.dark *),.custom-fields-component .dark\:bg-gray-800\/50:where(.dark,.dark *){background-color:var(--gray-800)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-gray-800\/50:where(.dark,.dark *){background-color:color-mix(in oklab,var(--gray-800) 50%,transparent)}}.custom-fields-component .dark\:bg-gray-900:where(.dark,.dark *){background-color:var(--gray-900)}.custom-fields-component .dark\:bg-info-400\/10:where(.dark,.dark *){background-color:var(--info-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-info-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--info-400) 10%,transparent)}}.custom-fields-component .dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:var(--primary-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-primary-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--primary-400) 10%,transparent)}}.custom-fields-component .dark\:bg-primary-500:where(.dark,.dark *),.custom-fields-component .dark\:bg-primary-500\/10:where(.dark,.dark *){background-color:var(--primary-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-primary-500\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--primary-500) 10%,transparent)}}.custom-fields-component .dark\:bg-primary-950\/50:where(.dark,.dark *){background-color:var(--primary-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-primary-950\/50:where(.dark,.dark *){background-color:color-mix(in oklab,var(--primary-950) 50%,transparent)}}.custom-fields-component .dark\:bg-warning-400\/10:where(.dark,.dark *){background-color:var(--warning-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-warning-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--warning-400) 10%,transparent)}}.custom-fields-component .dark\:bg-white\/5:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-white\/5:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .dark\:bg-white\/10:where(.dark,.dark *){background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:bg-white\/10:where(.dark,.dark *){background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .dark\:from-gray-700\/0:where(.dark,.dark *){--tw-gradient-from:var(--gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:from-gray-700\/0:where(.dark,.dark *){--tw-gradient-from:color-mix(in oklab, var(--gray-700) 0%, transparent)}}.custom-fields-component .dark\:from-gray-700\/0:where(.dark,.dark *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.custom-fields-component .dark\:from-gray-800\/0:where(.dark,.dark *){--tw-gradient-from:var(--gray-800)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:from-gray-800\/0:where(.dark,.dark *){--tw-gradient-from:color-mix(in oklab, var(--gray-800) 0%, transparent)}}.custom-fields-component .dark\:from-gray-800\/0:where(.dark,.dark *){--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.custom-fields-component .dark\:via-gray-700\/70:where(.dark,.dark *){--tw-gradient-via:var(--gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:via-gray-700\/70:where(.dark,.dark *){--tw-gradient-via:color-mix(in oklab, var(--gray-700) 70%, transparent)}}.custom-fields-component .dark\:via-gray-700\/70:where(.dark,.dark *){--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.custom-fields-component .dark\:via-gray-800\/70:where(.dark,.dark *){--tw-gradient-via:var(--gray-800)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:via-gray-800\/70:where(.dark,.dark *){--tw-gradient-via:color-mix(in oklab, var(--gray-800) 70%, transparent)}}.custom-fields-component .dark\:via-gray-800\/70:where(.dark,.dark *){--tw-gradient-via-stops:var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.custom-fields-component .dark\:to-gray-700:where(.dark,.dark *){--tw-gradient-to:var(--gray-700);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.custom-fields-component .dark\:to-gray-800:where(.dark,.dark *){--tw-gradient-to:var(--gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.custom-fields-component .dark\:text-danger-300:where(.dark,.dark *){color:var(--danger-300)}.custom-fields-component .dark\:text-gray-100:where(.dark,.dark *){color:var(--gray-100)}.custom-fields-component .dark\:text-gray-200:where(.dark,.dark *){color:var(--gray-200)}.custom-fields-component .dark\:text-gray-300:where(.dark,.dark *){color:var(--gray-300)}.custom-fields-component .dark\:text-gray-400:where(.dark,.dark *){color:var(--gray-400)}.custom-fields-component .dark\:text-gray-500:where(.dark,.dark *){color:var(--gray-500)}.custom-fields-component .dark\:text-info-300:where(.dark,.dark *){color:var(--info-300)}.custom-fields-component .dark\:text-neutral-400:where(.dark,.dark *){color:var(--color-neutral-400)}.custom-fields-component .dark\:text-primary-300:where(.dark,.dark *){color:var(--primary-300)}.custom-fields-component .dark\:text-primary-400:where(.dark,.dark *){color:var(--primary-400)}.custom-fields-component .dark\:text-warning-200:where(.dark,.dark *){color:var(--warning-200)}.custom-fields-component .dark\:text-warning-300:where(.dark,.dark *){color:var(--warning-300)}.custom-fields-component .dark\:text-white:where(.dark,.dark *){color:var(--color-white)}.custom-fields-component .dark\:decoration-gray-600:where(.dark,.dark *){-webkit-text-decoration-color:var(--gray-600);text-decoration-color:var(--gray-600)}.custom-fields-component .dark\:ring-primary-400\/20:where(.dark,.dark *){--tw-ring-color:var(--primary-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:ring-primary-400\/20:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--primary-400) 20%, transparent)}}.custom-fields-component .dark\:ring-primary-500:where(.dark,.dark *){--tw-ring-color:var(--primary-500)}.custom-fields-component .dark\:ring-warning-400\/20:where(.dark,.dark *){--tw-ring-color:var(--warning-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:ring-warning-400\/20:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--warning-400) 20%, transparent)}}.custom-fields-component .dark\:ring-white\/10:where(.dark,.dark *){--tw-ring-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:ring-white\/10:where(.dark,.dark *){--tw-ring-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .dark\:placeholder\:text-gray-500:where(.dark,.dark *)::placeholder{color:var(--gray-500)}@media (hover:hover){.custom-fields-component .dark\:hover\:bg-danger-500\/10:where(.dark,.dark *):hover{background-color:var(--danger-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-danger-500\/10:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--danger-500) 10%,transparent)}}.custom-fields-component .dark\:hover\:bg-gray-600:where(.dark,.dark *):hover{background-color:var(--gray-600)}.custom-fields-component .dark\:hover\:bg-gray-700:where(.dark,.dark *):hover{background-color:var(--gray-700)}.custom-fields-component .dark\:hover\:bg-gray-800:where(.dark,.dark *):hover,.custom-fields-component .dark\:hover\:bg-gray-800\/50:where(.dark,.dark *):hover{background-color:var(--gray-800)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-gray-800\/50:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--gray-800) 50%,transparent)}}.custom-fields-component .dark\:hover\:bg-primary-500\/10:where(.dark,.dark *):hover{background-color:var(--primary-500)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-primary-500\/10:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--primary-500) 10%,transparent)}}.custom-fields-component .dark\:hover\:bg-warning-400\/20:where(.dark,.dark *):hover{background-color:var(--warning-400)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-warning-400\/20:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--warning-400) 20%,transparent)}}.custom-fields-component .dark\:hover\:bg-white\/5:where(.dark,.dark *):hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-white\/5:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.custom-fields-component .dark\:hover\:bg-white\/10:where(.dark,.dark *):hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-white\/10:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.custom-fields-component .dark\:hover\:bg-white\/20:where(.dark,.dark *):hover{background-color:#fff3}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .dark\:hover\:bg-white\/20:where(.dark,.dark *):hover{background-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.custom-fields-component .dark\:hover\:text-gray-200:where(.dark,.dark *):hover{color:var(--gray-200)}.custom-fields-component .dark\:hover\:text-gray-300:where(.dark,.dark *):hover{color:var(--gray-300)}}.custom-fields-component .dark\:disabled\:text-gray-400:where(.dark,.dark *):disabled{color:var(--gray-400)}.custom-fields-component .\[\&_\.fi-badge\]\:ml-auto .fi-badge{margin-left:auto}.custom-fields-component .\[\&\:\:-webkit-scrollbar\]\:w-1\.5::-webkit-scrollbar{width:calc(var(--spacing)*1.5)}.custom-fields-component .\[\&\:\:-webkit-scrollbar-thumb\]\:rounded-full::-webkit-scrollbar-thumb{border-radius:3.40282e+38px}.custom-fields-component .\[\&\:\:-webkit-scrollbar-thumb\]\:bg-gray-300::-webkit-scrollbar-thumb{background-color:var(--gray-300)}.custom-fields-component .dark\:\[\&\:\:-webkit-scrollbar-thumb\]\:bg-gray-600:where(.dark,.dark *)::-webkit-scrollbar-thumb{background-color:var(--gray-600)}.custom-fields-component .fi-color-danger{--color-50:var(--danger-50);--color-100:var(--danger-100);--color-200:var(--danger-200);--color-300:var(--danger-300);--color-400:var(--danger-400);--color-500:var(--danger-500);--color-600:var(--danger-600);--color-700:var(--danger-700);--color-800:var(--danger-800);--color-900:var(--danger-900);--color-950:var(--danger-950)}.custom-fields-component .fi-color-gray{--color-50:var(--gray-50);--color-100:var(--gray-100);--color-200:var(--gray-200);--color-300:var(--gray-300);--color-400:var(--gray-400);--color-500:var(--gray-500);--color-600:var(--gray-600);--color-700:var(--gray-700);--color-800:var(--gray-800);--color-900:var(--gray-900);--color-950:var(--gray-950)}.custom-fields-component .fi-color-info{--color-50:var(--info-50);--color-100:var(--info-100);--color-200:var(--info-200);--color-300:var(--info-300);--color-400:var(--info-400);--color-500:var(--info-500);--color-600:var(--info-600);--color-700:var(--info-700);--color-800:var(--info-800);--color-900:var(--info-900);--color-950:var(--info-950)}.custom-fields-component .fi-color-primary{--color-50:var(--primary-50);--color-100:var(--primary-100);--color-200:var(--primary-200);--color-300:var(--primary-300);--color-400:var(--primary-400);--color-500:var(--primary-500);--color-600:var(--primary-600);--color-700:var(--primary-700);--color-800:var(--primary-800);--color-900:var(--primary-900);--color-950:var(--primary-950)}.custom-fields-component .fi-color-success{--color-50:var(--success-50);--color-100:var(--success-100);--color-200:var(--success-200);--color-300:var(--success-300);--color-400:var(--success-400);--color-500:var(--success-500);--color-600:var(--success-600);--color-700:var(--success-700);--color-800:var(--success-800);--color-900:var(--success-900);--color-950:var(--success-950)}.custom-fields-component .fi-color-warning{--color-50:var(--warning-50);--color-100:var(--warning-100);--color-200:var(--warning-200);--color-300:var(--warning-300);--color-400:var(--warning-400);--color-500:var(--warning-500);--color-600:var(--warning-600);--color-700:var(--warning-700);--color-800:var(--warning-800);--color-900:var(--warning-900);--color-950:var(--warning-950)}.custom-fields-component .fi-bg-color-50{--bg:var(--color-50)}.custom-fields-component .fi-bg-color-100{--bg:var(--color-100)}.custom-fields-component .fi-bg-color-200{--bg:var(--color-200)}.custom-fields-component .fi-bg-color-300{--bg:var(--color-300)}.custom-fields-component .fi-bg-color-400{--bg:var(--color-400)}.custom-fields-component .fi-bg-color-500{--bg:var(--color-500)}.custom-fields-component .fi-bg-color-600{--bg:var(--color-600)}.custom-fields-component .fi-bg-color-700{--bg:var(--color-700)}.custom-fields-component .fi-bg-color-800{--bg:var(--color-800)}.custom-fields-component .fi-bg-color-900{--bg:var(--color-900)}.custom-fields-component .fi-bg-color-950{--bg:var(--color-950)}.custom-fields-component .hover\:fi-bg-color-50{--hover-bg:var(--color-50)}.custom-fields-component .hover\:fi-bg-color-100{--hover-bg:var(--color-100)}.custom-fields-component .hover\:fi-bg-color-200{--hover-bg:var(--color-200)}.custom-fields-component .hover\:fi-bg-color-300{--hover-bg:var(--color-300)}.custom-fields-component .hover\:fi-bg-color-400{--hover-bg:var(--color-400)}.custom-fields-component .hover\:fi-bg-color-500{--hover-bg:var(--color-500)}.custom-fields-component .hover\:fi-bg-color-600{--hover-bg:var(--color-600)}.custom-fields-component .hover\:fi-bg-color-700{--hover-bg:var(--color-700)}.custom-fields-component .hover\:fi-bg-color-800{--hover-bg:var(--color-800)}.custom-fields-component .hover\:fi-bg-color-900{--hover-bg:var(--color-900)}.custom-fields-component .hover\:fi-bg-color-950{--hover-bg:var(--color-950)}.custom-fields-component .dark\:fi-bg-color-50{--dark-bg:var(--color-50)}.custom-fields-component .dark\:fi-bg-color-100{--dark-bg:var(--color-100)}.custom-fields-component .dark\:fi-bg-color-200{--dark-bg:var(--color-200)}.custom-fields-component .dark\:fi-bg-color-300{--dark-bg:var(--color-300)}.custom-fields-component .dark\:fi-bg-color-400{--dark-bg:var(--color-400)}.custom-fields-component .dark\:fi-bg-color-500{--dark-bg:var(--color-500)}.custom-fields-component .dark\:fi-bg-color-600{--dark-bg:var(--color-600)}.custom-fields-component .dark\:fi-bg-color-700{--dark-bg:var(--color-700)}.custom-fields-component .dark\:fi-bg-color-800{--dark-bg:var(--color-800)}.custom-fields-component .dark\:fi-bg-color-900{--dark-bg:var(--color-900)}.custom-fields-component .dark\:fi-bg-color-950{--dark-bg:var(--color-950)}.custom-fields-component .dark\:hover\:fi-bg-color-50{--dark-hover-bg:var(--color-50)}.custom-fields-component .dark\:hover\:fi-bg-color-100{--dark-hover-bg:var(--color-100)}.custom-fields-component .dark\:hover\:fi-bg-color-200{--dark-hover-bg:var(--color-200)}.custom-fields-component .dark\:hover\:fi-bg-color-300{--dark-hover-bg:var(--color-300)}.custom-fields-component .dark\:hover\:fi-bg-color-400{--dark-hover-bg:var(--color-400)}.custom-fields-component .dark\:hover\:fi-bg-color-500{--dark-hover-bg:var(--color-500)}.custom-fields-component .dark\:hover\:fi-bg-color-600{--dark-hover-bg:var(--color-600)}.custom-fields-component .dark\:hover\:fi-bg-color-700{--dark-hover-bg:var(--color-700)}.custom-fields-component .dark\:hover\:fi-bg-color-800{--dark-hover-bg:var(--color-800)}.custom-fields-component .dark\:hover\:fi-bg-color-900{--dark-hover-bg:var(--color-900)}.custom-fields-component .dark\:hover\:fi-bg-color-950{--dark-hover-bg:var(--color-950)}.custom-fields-component .fi-text-color-0{--text:oklch(100% 0 0)}.custom-fields-component .fi-text-color-50{--text:var(--color-50)}.custom-fields-component .fi-text-color-100{--text:var(--color-100)}.custom-fields-component .fi-text-color-200{--text:var(--color-200)}.custom-fields-component .fi-text-color-300{--text:var(--color-300)}.custom-fields-component .fi-text-color-400{--text:var(--color-400)}.custom-fields-component .fi-text-color-500{--text:var(--color-500)}.custom-fields-component .fi-text-color-600{--text:var(--color-600)}.custom-fields-component .fi-text-color-700{--text:var(--color-700)}.custom-fields-component .fi-text-color-800{--text:var(--color-800)}.custom-fields-component .fi-text-color-900{--text:var(--color-900)}.custom-fields-component .fi-text-color-950{--text:var(--color-950)}.custom-fields-component .hover\:fi-text-color-0{--hover-text:oklch(100% 0 0)}.custom-fields-component .hover\:fi-text-color-50{--hover-text:var(--color-50)}.custom-fields-component .hover\:fi-text-color-100{--hover-text:var(--color-100)}.custom-fields-component .hover\:fi-text-color-200{--hover-text:var(--color-200)}.custom-fields-component .hover\:fi-text-color-300{--hover-text:var(--color-300)}.custom-fields-component .hover\:fi-text-color-400{--hover-text:var(--color-400)}.custom-fields-component .hover\:fi-text-color-500{--hover-text:var(--color-500)}.custom-fields-component .hover\:fi-text-color-600{--hover-text:var(--color-600)}.custom-fields-component .hover\:fi-text-color-700{--hover-text:var(--color-700)}.custom-fields-component .hover\:fi-text-color-800{--hover-text:var(--color-800)}.custom-fields-component .hover\:fi-text-color-900{--hover-text:var(--color-900)}.custom-fields-component .hover\:fi-text-color-950{--hover-text:var(--color-950)}.custom-fields-component .dark\:fi-text-color-0{--dark-text:oklch(100% 0 0)}.custom-fields-component .dark\:fi-text-color-50{--dark-text:var(--color-50)}.custom-fields-component .dark\:fi-text-color-100{--dark-text:var(--color-100)}.custom-fields-component .dark\:fi-text-color-200{--dark-text:var(--color-200)}.custom-fields-component .dark\:fi-text-color-300{--dark-text:var(--color-300)}.custom-fields-component .dark\:fi-text-color-400{--dark-text:var(--color-400)}.custom-fields-component .dark\:fi-text-color-500{--dark-text:var(--color-500)}.custom-fields-component .dark\:fi-text-color-600{--dark-text:var(--color-600)}.custom-fields-component .dark\:fi-text-color-700{--dark-text:var(--color-700)}.custom-fields-component .dark\:fi-text-color-800{--dark-text:var(--color-800)}.custom-fields-component .dark\:fi-text-color-900{--dark-text:var(--color-900)}.custom-fields-component .dark\:fi-text-color-950{--dark-text:var(--color-950)}.custom-fields-component .dark\:hover\:fi-text-color-0{--dark-hover-text:oklch(100% 0 0)}.custom-fields-component .dark\:hover\:fi-text-color-50{--dark-hover-text:var(--color-50)}.custom-fields-component .dark\:hover\:fi-text-color-100{--dark-hover-text:var(--color-100)}.custom-fields-component .dark\:hover\:fi-text-color-200{--dark-hover-text:var(--color-200)}.custom-fields-component .dark\:hover\:fi-text-color-300{--dark-hover-text:var(--color-300)}.custom-fields-component .dark\:hover\:fi-text-color-400{--dark-hover-text:var(--color-400)}.custom-fields-component .dark\:hover\:fi-text-color-500{--dark-hover-text:var(--color-500)}.custom-fields-component .dark\:hover\:fi-text-color-600{--dark-hover-text:var(--color-600)}.custom-fields-component .dark\:hover\:fi-text-color-700{--dark-hover-text:var(--color-700)}.custom-fields-component .dark\:hover\:fi-text-color-800{--dark-hover-text:var(--color-800)}.custom-fields-component .dark\:hover\:fi-text-color-900{--dark-hover-text:var(--color-900)}.custom-fields-component .dark\:hover\:fi-text-color-950{--dark-hover-text:var(--color-950)}.custom-fields-component .fi-sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.custom-fields-component .fi-prose{--prose-color:var(--color-gray-700);--prose-heading-color:var(--color-gray-950);--prose-strong-color:var(--color-gray-950);--prose-link-color:var(--color-gray-950);--prose-code-color:var(--color-gray-950);--prose-marker-color:var(--color-gray-700)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose{--prose-marker-color:color-mix(in oklab, var(--color-gray-700) 25%, transparent)}}.custom-fields-component .fi-prose{--prose-link-underline-color:var(--color-primary-400);--prose-th-borders:var(--color-gray-300);--prose-td-borders:var(--color-gray-200);--prose-hr-color:var(--color-gray-950)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose{--prose-hr-color:color-mix(in oklab, var(--color-gray-950) 5%, transparent)}}.custom-fields-component .fi-prose{--prose-blockquote-border-color:var(--color-gray-300);--prose-pre-bg:var(--color-gray-100)}.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-color:var(--color-gray-300);--prose-heading-color:var(--color-white);--prose-strong-color:var(--color-white);--prose-link-color:var(--color-white);--prose-code-color:var(--color-white);--prose-marker-color:var(--color-gray-300)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-marker-color:color-mix(in oklab, var(--color-gray-300) 35%, transparent)}}.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-link-underline-color:var(--color-sky-400);--prose-th-borders:var(--color-gray-600);--prose-td-borders:var(--color-gray-700);--prose-hr-color:oklab(100% 0 5.96046e-8/.1)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-hr-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-blockquote-border-color:var(--color-gray-600);--prose-pre-bg:var(--color-gray-900)}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose:where(.dark,.dark *){--prose-pre-bg:color-mix(in oklab, var(--color-gray-900) 40%, transparent)}}.custom-fields-component .fi-prose{color:var(--prose-color);font-size:var(--text-sm);line-height:1.5}.custom-fields-component .fi-prose img+img{margin-top:0}.custom-fields-component .fi-prose :where(:not(.fi-not-prose,.fi-not-prose *,br))+:where(:not(.fi-not-prose,.fi-not-prose *,br)){margin-top:calc(var(--spacing)*4)}.custom-fields-component .fi-prose p br{margin:0}.custom-fields-component .fi-prose h1:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:var(--text-3xl);letter-spacing:-.025em;color:var(--prose-heading-color);line-height:1.2;font-weight:var(--font-weight-bold)}.custom-fields-component .fi-prose h2:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:var(--text-2xl);letter-spacing:-.025em;color:var(--prose-heading-color);line-height:1.33333;font-weight:var(--font-weight-bold)}.custom-fields-component .fi-prose h3:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:var(--text-xl);color:var(--prose-heading-color);line-height:1.4;font-weight:var(--font-weight-bold)}.custom-fields-component .fi-prose h4:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:var(--text-lg);color:var(--prose-heading-color);line-height:1.55556;font-weight:var(--font-weight-bold)}.custom-fields-component .fi-prose h5:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:var(--text-base);color:var(--prose-heading-color);line-height:1.5;font-weight:var(--font-weight-bold)}.custom-fields-component .fi-prose h6:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:var(--text-sm);color:var(--prose-heading-color);line-height:1.42857;font-weight:var(--font-weight-bold)}.custom-fields-component .fi-prose :is(h2,h3,h4,h5,h6):where(:not(.fi-not-prose,.fi-not-prose *)){scroll-margin-top:calc(var(--topbar-height,4rem) + var(--spacing)*16)}@media (min-width:64rem){.custom-fields-component .fi-prose :is(h2,h3,h4,h5,h6):where(:not(.fi-not-prose,.fi-not-prose *)){scroll-margin-top:calc(var(--topbar-height,4rem) + var(--spacing)*2)}}.custom-fields-component .fi-prose ol:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-start:calc(var(--spacing)*6);list-style-type:decimal}.custom-fields-component .fi-prose ul:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-start:calc(var(--spacing)*6);list-style-type:disc}.custom-fields-component .fi-prose ol li:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose ul li:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-start:calc(var(--spacing)*3)}.custom-fields-component .fi-prose ol li+li:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose ul li+li:where(:not(.fi-not-prose,.fi-not-prose *)){margin-top:calc(var(--spacing)*4)}.custom-fields-component .fi-prose ol li:where(:not(.fi-not-prose,.fi-not-prose *))::marker{color:var(--prose-marker-color)}.custom-fields-component .fi-prose ul li:where(:not(.fi-not-prose,.fi-not-prose *))::marker{color:var(--prose-marker-color)}.custom-fields-component .fi-prose a:not(:where(:is(h2,h3,h4,h5,h6) *)):where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-link-color);font-weight:var(--font-weight-semibold);text-underline-offset:3px;text-decoration:underline;-webkit-text-decoration-color:var(--prose-link-underline-color);text-decoration-color:var(--prose-link-underline-color);text-decoration-thickness:1px}.custom-fields-component .fi-prose a:not(:where(:is(h2,h3,h4,h5,h6) *)):where(:not(.fi-not-prose,.fi-not-prose *)) code{font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-prose a:hover:where(:not(.fi-not-prose,.fi-not-prose *)){text-decoration-thickness:2px}.custom-fields-component .fi-prose strong:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-strong-color);font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-prose code:where(:not(.fi-not-prose,.fi-not-prose *)){font-variant-ligatures:none;font-family:var(--font-mono);font-weight:var(--font-weight-medium);color:var(--prose-code-color)}.custom-fields-component .fi-prose :where(h2,h3,h4,h5,h6) code:where(:not(.fi-not-prose,.fi-not-prose *)){font-weight:var(--font-weight-semibold)}.custom-fields-component .fi-prose code:where(:not(.fi-not-prose,.fi-not-prose *)):after,.custom-fields-component .fi-prose code:where(:not(.fi-not-prose,.fi-not-prose *)):before{content:"`";display:inline}.custom-fields-component .fi-prose pre:where(:not(.fi-not-prose,.fi-not-prose *)){margin-top:calc(var(--spacing)*4);margin-bottom:calc(var(--spacing)*10);border-radius:var(--radius-lg);padding-top:calc(var(--spacing)*3);padding-inline-end:calc(var(--spacing)*4);padding-bottom:calc(var(--spacing)*3);background-color:var(--prose-pre-bg);padding-inline-start:calc(var(--spacing)*4)}.custom-fields-component .fi-prose pre code *+:where(:not(.fi-not-prose,.fi-not-prose *)){margin-top:0}.custom-fields-component .fi-prose pre code:where(:not(.fi-not-prose,.fi-not-prose *)):after,.custom-fields-component .fi-prose pre code:where(:not(.fi-not-prose,.fi-not-prose *)):before{content:none}.custom-fields-component .fi-prose pre code:where(:not(.fi-not-prose,.fi-not-prose *)){font-variant-ligatures:none;font-family:var(--font-mono);font-size:var(--text-sm);line-height:2}.custom-fields-component .fi-prose table:where(:not(.fi-not-prose,.fi-not-prose *)){table-layout:auto;width:100%;font-size:var(--text-sm);margin-top:2em;margin-bottom:2em;line-height:1.4}.custom-fields-component .fi-prose thead:where(:not(.fi-not-prose,.fi-not-prose *)){border-bottom-width:1px;border-bottom-color:var(--prose-th-borders)}.custom-fields-component .fi-prose thead th:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--prose-heading-color);vertical-align:bottom;padding-inline-end:.6em;padding-bottom:.8em;padding-inline-start:.6em;font-weight:600}.custom-fields-component .fi-prose thead th:first-child:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-start:0}.custom-fields-component .fi-prose thead th:last-child:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-end:0}.custom-fields-component .fi-prose tbody tr:where(:not(.fi-not-prose,.fi-not-prose *)){border-bottom-width:1px;border-bottom-color:var(--prose-td-borders)}.custom-fields-component .fi-prose tbody tr:last-child:where(:not(.fi-not-prose,.fi-not-prose *)){border-bottom-width:0}.custom-fields-component .fi-prose tbody td:where(:not(.fi-not-prose,.fi-not-prose *)){vertical-align:baseline}.custom-fields-component .fi-prose tfoot:where(:not(.fi-not-prose,.fi-not-prose *)){border-top-width:1px;border-top-color:var(--prose-th-borders)}.custom-fields-component .fi-prose tfoot td:where(:not(.fi-not-prose,.fi-not-prose *)){vertical-align:top}.custom-fields-component .fi-prose tbody td:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose tfoot td:where(:not(.fi-not-prose,.fi-not-prose *)){padding-top:.8em;padding-inline-end:.6em;padding-bottom:.8em;padding-inline-start:.6em}.custom-fields-component .fi-prose tbody td:first-child:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose tfoot td:first-child:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-start:0}.custom-fields-component .fi-prose tbody td:last-child:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose tfoot td:last-child:where(:not(.fi-not-prose,.fi-not-prose *)){padding-inline-end:0}.custom-fields-component .fi-prose td:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose th:where(:not(.fi-not-prose,.fi-not-prose *)){text-align:start}.custom-fields-component .fi-prose td code:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:.8125rem}.custom-fields-component .fi-prose hr:where(:not(.fi-not-prose,.fi-not-prose *)){border-color:var(--prose-hr-color);margin-block:calc(var(--spacing)*8)}.custom-fields-component .fi-prose hr:where(:not(.fi-not-prose,.fi-not-prose *))+h2{margin-top:calc(var(--spacing)*8)}.custom-fields-component .fi-prose blockquote{border-inline-start-width:.25rem;border-inline-start-color:var(--prose-blockquote-border-color);padding-inline-start:calc(var(--spacing)*4);font-style:italic}.custom-fields-component .fi-prose blockquote p:first-of-type:before{content:open-quote}.custom-fields-component .fi-prose blockquote p:last-of-type:after{content:close-quote}.custom-fields-component .fi-prose figure:where(:not(.fi-not-prose,.fi-not-prose *)) figcaption:where(:not(.fi-not-prose,.fi-not-prose *)){margin-top:calc(var(--spacing)*3);text-align:center;font-size:var(--text-sm);line-height:var(--text-sm--line-height);color:var(--prose-color);font-style:italic}@supports (color:color-mix(in lab,red,red)){.custom-fields-component .fi-prose figure:where(:not(.fi-not-prose,.fi-not-prose *)) figcaption:where(:not(.fi-not-prose,.fi-not-prose *)){color:color-mix(in oklab,var(--prose-color) 75%,transparent)}}.custom-fields-component .fi-prose :first-child:where(:not(.fi-not-prose,.fi-not-prose *)){margin-top:0}.custom-fields-component .fi-prose :last-child:where(:not(.fi-not-prose,.fi-not-prose *)){margin-bottom:0}.custom-fields-component .fi-prose .color:where(:not(.fi-not-prose,.fi-not-prose *)){color:var(--color)}.custom-fields-component .fi-prose .color:where(:not(.fi-not-prose,.fi-not-prose *)):where(.dark,.dark *){color:var(--dark-color)}.custom-fields-component .fi-prose .lead:where(:not(.fi-not-prose,.fi-not-prose *)){font-size:var(--text-base)}.custom-fields-component .fi-prose a[data-type=mention]:where(:not(.fi-not-prose,.fi-not-prose *)),.custom-fields-component .fi-prose span[data-type=mention]:where(:not(.fi-not-prose,.fi-not-prose *)){font-weight:var(--font-weight-semibold);white-space:nowrap;margin-block:0;display:inline-block}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *)){gap:calc(var(--spacing)*4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=default]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=default]>.grid-layout-col{grid-column:var(--col-span)}@media (min-width:40rem){.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=sm]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=sm]>.grid-layout-col{grid-column:var(--col-span)}}@media (min-width:48rem){.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=md]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=md]>.grid-layout-col{grid-column:var(--col-span)}}@media (min-width:64rem){.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=lg]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=lg]>.grid-layout-col{grid-column:var(--col-span)}}@media (min-width:80rem){.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=xl]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint=xl]>.grid-layout-col{grid-column:var(--col-span)}}@media (min-width:96rem){.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint="2xl"]{grid-template-columns:var(--cols)}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))[data-from-breakpoint="2xl"]>.grid-layout-col{grid-column:var(--col-span)}}.custom-fields-component .fi-prose .grid-layout:where(:not(.fi-not-prose,.fi-not-prose *))>.grid-layout-col{min-width:0;margin-top:0}.custom-fields-component .fi-skip-link:focus{clip-path:none;white-space:normal;width:auto;height:auto;inset-inline-start:calc(var(--spacing)*4);top:calc(var(--spacing)*4);z-index:50;border-radius:var(--radius-lg);background-color:var(--color-white);padding:0;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--gray-950);--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-ring-color:var(--primary-600);--tw-outline-style:none;outline-style:none;margin:0;position:fixed;overflow:visible}.custom-fields-component .fi-skip-link:focus:where(.dark,.dark *){background-color:var(--gray-900);color:var(--color-white)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-ease{syntax:"*";inherits:false}@property --tw-divide-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(1turn)}}@keyframes pulse{50%{opacity:.5}} \ No newline at end of file diff --git a/resources/lang/en/custom-fields.php b/resources/lang/en/custom-fields.php index 1de8f52f..b0dbe61f 100644 --- a/resources/lang/en/custom-fields.php +++ b/resources/lang/en/custom-fields.php @@ -71,13 +71,56 @@ 'visibility_settings' => 'Visibility', 'data_settings' => 'Data Handling', 'appearance_settings' => 'Appearance', - 'lookup_type' => [ - 'label' => 'Lookup Type', + 'record' => [ + 'label' => 'Related Records', + 'target' => 'Related Entity', + 'target_help' => 'The entity whose records this field links to. It cannot be changed once the field exists.', + 'cardinality' => 'How Many Records', + 'cardinality_help' => 'How many records each side of the relationship can hold.', + 'is_symmetric' => 'Same Field Both Ways', + 'is_symmetric_help' => 'One field read from both ends, for relationships that mean the same thing in either direction.', + 'paired_field_name' => 'Field on the Related Entity', + 'paired_field_name_help' => 'Adds a field on the related entity that shows the other end of the same link. Leave it empty for a one-way field.', + 'paired_section' => 'Section for That Field', + 'paired_section_help' => 'The section the paired field is added to.', + 'keep_first' => 'Keep only the first linked record', + 'keep_first_help' => 'This field now holds a single record. The first record each side already links to is kept and the rest are unlinked.', + 'this_entity' => 'This entity', + 'related_entity' => 'Related entity', + 'unknown_entity' => 'Unregistered entity', + 'untitled_field' => 'Untitled field', + 'field_on_this_entity' => 'Field on this entity', + 'sync_banner' => 'Both fields read the same link: connect from either side and the other side shows it.', + 'sync_banner_symmetric' => 'One field read from both ends: every record it links to lists this record back.', + 'sync_banner_one_way' => 'One-way field: nothing is added to the related entity. Name a field below to show the link from the other side too.', + 'sentence_placeholder' => 'Pick a related entity to see how the two sides connect.', + 'sentence' => [ + 'one_to_one' => 'One :source links to one :target.', + 'one_to_many' => 'One :source links to many :target.', + 'many_to_one' => 'Many :source link to one :target.', + 'many_to_many' => 'Many :source link to many :target.', + ], + ], + 'pair' => [ + 'paired' => 'Paired with :field on :entity', + 'symmetric' => 'Read from both ends on :entity', + 'one_way' => 'One way to :entity', ], 'options' => [ 'label' => 'Options', 'add' => 'Add Option', + 'category' => 'Category', + 'category_placeholder' => 'Uncategorised', + 'paste' => 'Paste a list', + 'paste_modal_heading' => 'Paste a list of options', + 'paste_names' => 'One option per line', + 'paste_names_help' => 'Names only. A name you already have is skipped, and at most :max lines are read at once.', + 'paste_submit' => 'Add options', + 'pasted' => ':added added, :duplicates skipped as duplicates', + 'pasted_capped' => 'Only the first :max lines were read.', ], + 'advanced' => 'Advanced', + 'advanced_description' => 'The machine code other systems use to address this field. It is generated from the name.', 'add_field' => 'Add Field', 'search_placeholder' => 'Search fields...', 'type_search_prompt' => 'Search field types...', @@ -384,6 +427,7 @@ 'fields_no_sections' => [ 'heading' => 'No custom fields yet', 'description' => 'Click the button below to add your first custom field.', + 'education' => 'A custom field adds a column of your own to every record of this entity: a status, a renewal date, a link to another record. It shows up in the form, the table and the API straight away.', 'icon' => 'heroicon-o-squares-plus', ], 'search_no_results' => [ @@ -407,6 +451,8 @@ 'add' => 'Add', 'click_to_add' => 'Click to add', 'searching' => 'Searching...', + 'reorder' => 'Drag to reorder', + 'entity_tabs' => 'Entity tabs', ], 'email' => [ @@ -439,6 +485,44 @@ 'search_placeholder' => 'Search records...', 'add_record_placeholder' => 'Add record...', 'empty_state_label' => 'Select record...', + 'no_records' => 'Not linked', + 'more_records' => '{1} :count more|[2,*] :count more', + 'search_label' => 'Search records', + 'select_label' => 'Select records', + 'clear' => 'Clear selection', + 'remove' => 'Remove :record', + 'move_up' => 'Move :record earlier', + 'move_down' => 'Move :record later', + 'no_results' => 'No records found', + 'announce_selected' => 'selected', + 'announce_deselected' => 'deselected', + 'announce_count' => '{1} :count record linked|[2,*] :count records linked', + 'none_available' => 'No records available', + 'short_search' => 'Type at least :count characters to search', + 'create_new' => 'Create a new :entity', + 'steal_heading' => 'Move this record?', + 'steal_confirm' => 'Move it here', + 'steal_cancel' => 'Leave it where it is', + ], + + 'relationships' => [ + 'errors' => [ + 'unknown_target' => 'One or more of the selected records could not be found.', + 'conflict' => 'This link conflicts with a concurrent change. Reload and try again.', + 'single_value' => 'This relationship holds a single record.', + 'already_linked' => ':record is already linked to :holder. Confirm the replacement to move it.', + 'keep_first_required' => 'This relationship holds several records. Confirm keeping the first one to narrow it.', + ], + 'provenance' => [ + 'by_actor' => 'Linked by :actor, :time', + 'by_source' => 'Linked :source, :time', + ], + 'sources' => [ + 'user' => 'by hand', + 'import' => 'by an import', + 'migration' => 'by a migration', + 'ai_inferred' => 'by the assistant', + ], ], 'enums' => [ @@ -497,6 +581,27 @@ 'scoped_management' => 'Entity custom fields are managed per-parent record, not on the global management page', ], ], + 'import_date_format' => [ + 'iso' => 'ISO standard', + 'european' => 'European (day first)', + 'american' => 'American (month first)', + ], + 'import_number_format' => [ + 'point' => 'Point', + 'comma' => 'Comma', + ], + 'option_category' => [ + 'unstarted' => 'Not started', + 'started' => 'Started', + 'completed' => 'Completed', + 'cancelled' => 'Cancelled', + ], + 'relationship_cardinality' => [ + 'one_to_one' => 'One to one', + 'one_to_many' => 'One to many', + 'many_to_one' => 'Many to one', + 'many_to_many' => 'Many to many', + ], 'visibility_logic' => [ 'all' => 'All conditions must be met (AND)', 'any' => 'Any condition must be met (OR)', @@ -545,6 +650,7 @@ 'toggle_buttons' => 'Toggle Buttons', 'number' => 'Number', 'record' => 'Record', + 'relationship' => 'Relationship', 'file_upload' => 'File Upload', 'multi_select' => 'Multi Select', 'checkbox_list' => 'Checkbox List', @@ -562,9 +668,44 @@ 'textarea' => 'Textarea', 'markdown_editor' => 'Markdown Editor', 'select' => 'Select', + 'status' => 'Status', 'tags_input' => 'Tags Input', ], + 'field_type_descriptions' => [ + 'toggle' => 'A single on or off switch.', + 'toggle_buttons' => 'One choice, shown as buttons side by side.', + 'number' => 'A number, whole or decimal.', + 'record' => 'A one-way link to records of another entity.', + 'relationship' => 'A two-way link, with a matching field on the other entity.', + 'file_upload' => 'One or more uploaded files.', + 'multi_select' => 'Several choices from a list you define.', + 'checkbox_list' => 'Several choices, all shown at once.', + 'color_picker' => 'A colour, picked or typed as a hex value.', + 'checkbox' => 'A single box to tick.', + 'date' => 'A calendar date.', + 'radio' => 'One choice, all options visible.', + 'link' => 'A web address, shown as a link.', + 'text' => 'A single line of text.', + 'date_time' => 'A date with a time of day.', + 'rich_editor' => 'Formatted text with headings, lists and links.', + 'currency' => 'An amount of money in a chosen currency.', + 'phone' => 'A phone number, validated by country.', + 'email' => 'An email address.', + 'textarea' => 'Several lines of plain text.', + 'markdown_editor' => 'Text written in markdown.', + 'select' => 'One choice from a list you define.', + 'status' => 'One choice from a list of workflow states you define.', + 'tags_input' => 'Free-form tags, typed one at a time.', + ], + + 'field_type_picker' => [ + 'search_placeholder' => 'Search field types...', + 'no_results' => 'No field type matches that search.', + 'selected' => 'Selected', + 'locked' => 'A field keeps the type it was created with.', + ], + 'currency' => [ 'fieldset' => 'Currency Settings', 'currency' => 'Currency', diff --git a/resources/views/filament/pages/custom-fields-management.blade.php b/resources/views/filament/pages/custom-fields-management.blade.php index a7b4d98f..5f71aa29 100644 --- a/resources/views/filament/pages/custom-fields-management.blade.php +++ b/resources/views/filament/pages/custom-fields-management.blade.php @@ -1,29 +1,22 @@ @if($this->isSectionsDisabled) - {{-- Flat layout mode: vertical tabs left, fields right --}} -
- {{-- Left side: Vertical entity tabs --}} -
- - @foreach ($this->entityTypes as $key => $label) - @php - $entity = \Relaticle\CustomFields\Facades\Entities::getEntity($key); - $fieldCount = $this->entityFieldCounts[$key] ?? 0; - @endphp - - {{ $label }} - - @endforeach + {{-- Flat layout mode: entity rail beside the fields, stacked on a narrow viewport --}} +
+ {{-- A rail 200px wide would leave the table unreadable on a phone, so below md the + same entities scroll horizontally above it. --}} +
+ + @include('custom-fields::filament.pages.partials.entity-tabs')
- {{-- Right side: Fields (no sections needed) --}} -
+ + +
@livewire('manage-fields-table', [ 'entityType' => $this->currentEntityType, ], key('manage-fields-table-' . $this->currentEntityType)) diff --git a/resources/views/filament/pages/partials/entity-tabs.blade.php b/resources/views/filament/pages/partials/entity-tabs.blade.php new file mode 100644 index 00000000..b16f2dc5 --- /dev/null +++ b/resources/views/filament/pages/partials/entity-tabs.blade.php @@ -0,0 +1,13 @@ +@foreach ($this->entityTypes as $key => $label) + @php + $entity = \Relaticle\CustomFields\Facades\Entities::getEntity($key); + @endphp + + {{ $label }} + +@endforeach diff --git a/resources/views/flavors/polished/attribute-table.blade.php b/resources/views/flavors/polished/attribute-table.blade.php new file mode 100644 index 00000000..c8b13633 --- /dev/null +++ b/resources/views/flavors/polished/attribute-table.blade.php @@ -0,0 +1,136 @@ +@php + $pairs = $this->relationshipPairs; + $activeFields = $this->activeFields; + $inactiveFields = $this->inactiveFields; + $isEmpty = $activeFields->count() === 0 && $inactiveFields->count() === 0; +@endphp + +
+
+
+ + + +
+ + {{ $this->createFieldAction() }} +
+ +
+ + +
+ @unless ($isEmpty) +
+
+
+
+
{{ __('custom-fields::custom-fields.field.form.name') }}
+
{{ __('custom-fields::custom-fields.field.form.type') }}
+
{{ __('custom-fields::custom-fields.common.properties') }}
+
+
+ +
+ @foreach ($activeFields as $field) + @include('custom-fields::flavors.polished.partials.attribute-row', [ + 'field' => $field, + 'pair' => $pairs[(string) $field->getKey()] ?? null, + 'sortable' => true, + ]) + @endforeach +
+ + @if ($inactiveFields->count()) +
+ + +
+ @foreach ($inactiveFields as $field) + @include('custom-fields::flavors.polished.partials.attribute-row', [ + 'field' => $field, + 'pair' => $pairs[(string) $field->getKey()] ?? null, + 'sortable' => false, + ]) + @endforeach +
+
+ @endif +
+
+ @else +
+
+
+ +
+

+ {{ filled($search) + ? __('custom-fields::custom-fields.empty_states.search_no_results.heading') + : __('custom-fields::custom-fields.empty_states.fields_no_sections.heading') }} +

+

+ {{ filled($search) + ? __('custom-fields::custom-fields.empty_states.search_no_results.description') + : __('custom-fields::custom-fields.empty_states.fields_no_sections.description') }} +

+ + @unless (filled($search)) +

+ {{ __('custom-fields::custom-fields.empty_states.fields_no_sections.education') }} +

+ @endunless +
+
+ @endunless +
+
+ + +
diff --git a/resources/views/flavors/polished/partials/attribute-row.blade.php b/resources/views/flavors/polished/partials/attribute-row.blade.php new file mode 100644 index 00000000..9a84bb28 --- /dev/null +++ b/resources/views/flavors/polished/partials/attribute-row.blade.php @@ -0,0 +1,109 @@ +@php + $isActive = $field->isActive(); + $isSystemDefined = $field->isSystemDefined(); + + $pairSentence = $pair === null ? null : match (true) { + $pair['symmetric'] => __('custom-fields::custom-fields.field.form.pair.symmetric', ['entity' => $pair['entity']]), + $pair['partner_name'] !== null => __('custom-fields::custom-fields.field.form.pair.paired', ['field' => $pair['partner_name'], 'entity' => $pair['entity']]), + default => __('custom-fields::custom-fields.field.form.pair.one_way', ['entity' => $pair['entity']]), + }; +@endphp + +
+
+ @if ($sortable) +
+ +
+ @endif +
+ +
+ @if ($field->typeData?->icon) + + @endif + {{ $field->name }} +
+ +
+ {{ $field->typeData?->label }} + + @if ($pairSentence !== null) + {{-- The column is too narrow for the sentence, and which entity the field pairs to + is the whole content, so it wraps instead of being cut. --}} + + + @endif +
+ +
+ @if ($isSystemDefined) + + + {{ __('custom-fields::custom-fields.common.system') }} + + @endif + + @if ($field->settings?->unique_per_entity_type) + + {{ __('custom-fields::custom-fields.common.unique') }} + + @endif + + @if ($field->validation_rules?->has('required')) + + {{ __('custom-fields::custom-fields.common.required') }} + + @endif + + @unless ($isActive) + + {{ __('custom-fields::custom-fields.common.archived') }} + + @endunless +
+ +
+ @unless ($isActive) + {{ ($this->activateFieldAction)(['fieldId' => $field->getKey()]) }} + @endunless + + +
+
diff --git a/resources/views/flavors/polished/partials/record-chip.blade.php b/resources/views/flavors/polished/partials/record-chip.blade.php new file mode 100644 index 00000000..d9eb4957 --- /dev/null +++ b/resources/views/flavors/polished/partials/record-chip.blade.php @@ -0,0 +1,31 @@ +@php + $chipTag = filled($chip['url']) ? 'a' : 'span'; +@endphp + +<{{ $chipTag }} + @if (filled($chip['url'])) href="{{ $chip['url'] }}" x-on:click.stop @endif + @if (filled($chip['provenance'])) + title="{{ $chip['provenance'] }}" + data-provenance="{{ $chip['provenance'] }}" + @endif + class="fi-cf-record-chip inline-flex max-w-full items-center gap-1.5 rounded-md bg-gray-50 py-1 ps-1 pe-2 text-sm text-gray-950 ring-1 ring-gray-950/5 transition hover:bg-gray-100 dark:bg-white/5 dark:text-white dark:ring-white/10 dark:hover:bg-white/10" +> + @if (filled($chip['avatarUrl'])) + + @else + + @endif + + {{ $chip['name'] }} + + @if (filled($chip['provenance'])) + {{ $chip['provenance'] }} + @endif + diff --git a/resources/views/flavors/polished/partials/type-picker-grid.blade.php b/resources/views/flavors/polished/partials/type-picker-grid.blade.php new file mode 100644 index 00000000..92536d99 --- /dev/null +++ b/resources/views/flavors/polished/partials/type-picker-grid.blade.php @@ -0,0 +1,108 @@ +@php + $searchIndex = array_map( + static fn (array $choice): array => [ + 'key' => $choice['key'], + 'haystack' => mb_strtolower($choice['label'].' '.($choice['description'] ?? '')), + ], + $choices, + ); +@endphp + +
+ @if ($isDisabled) +

+ {{ __('custom-fields::custom-fields.field_type_picker.locked') }} +

+ @else + + + + @endif + +
+ @foreach ($choices as $choice) + + @endforeach +
+ +

+ {{ __('custom-fields::custom-fields.field_type_picker.no_results') }} +

+
diff --git a/resources/views/flavors/polished/record-chips.blade.php b/resources/views/flavors/polished/record-chips.blade.php new file mode 100644 index 00000000..0c85225d --- /dev/null +++ b/resources/views/flavors/polished/record-chips.blade.php @@ -0,0 +1,52 @@ +@php + /** @var array $chips */ + $maxVisible = $maxVisible ?? count($chips); + $visibleChips = array_slice($chips, 0, max(1, $maxVisible)); + $hiddenChips = array_slice($chips, count($visibleChips)); +@endphp + +
+ @forelse ($visibleChips as $chip) + @include('custom-fields::flavors.polished.partials.record-chip', ['chip' => $chip]) + @empty + + {{ __('custom-fields::custom-fields.record.no_records') }} + + @endforelse + + @if ($hiddenChips !== []) +
+ + +
+
+ @foreach ($hiddenChips as $chip) + @include('custom-fields::flavors.polished.partials.record-chip', ['chip' => $chip]) + @endforeach +
+
+
+ @endif +
diff --git a/resources/views/flavors/polished/record-picker.blade.php b/resources/views/flavors/polished/record-picker.blade.php new file mode 100644 index 00000000..fb4435f2 --- /dev/null +++ b/resources/views/flavors/polished/record-picker.blade.php @@ -0,0 +1,252 @@ +@php + use Relaticle\CustomFields\Data\RecordLinkPayload; + + $fieldWrapperView = $getFieldWrapperView(); + $isDisabled = $isDisabled(); + $statePath = $getStatePath(); + $allowMultiple = $getAllowMultiple(); + $maxValues = $getMaxValues(); + $maxVisiblePills = $getMaxVisiblePills(); + $emptyStateLabel = $getEmptyStateLabel(); + $placeholder = $getPlaceholder() ?? __('custom-fields::custom-fields.record.search_placeholder'); + $key = $getKey(); + $minSearchLength = $getMinSearchLength(); + $shortSearchMessage = __('custom-fields::custom-fields.record.short_search', ['count' => $minSearchLength]); + $checksHolderConflicts = $checksHolderConflicts(); + $createUrl = $getCreateUrl(); + $createLabel = $getCreateLabel(); + + // A confirmed move travels as a map naming the record it was given for, so a failed + // validation round trip brings back that record and not whichever one sorts first. + $state = $getState() ?? []; + $selectedIds = array_filter(is_array($state) ? ($state['ids'] ?? $state) : []); + $confirmedStealIds = RecordLinkPayload::confirmedIds(is_array($state) ? $state : [], $selectedIds); + // A pluralized key cannot be read by __(), so both forms are chosen server-side and the + // client picks between them by count. + $overflowLabels = [ + 'one' => trans_choice('custom-fields::custom-fields.record.more_records', 1, ['count' => ':count']), + 'many' => trans_choice('custom-fields::custom-fields.record.more_records', 2, ['count' => ':count']), + ]; + $countLabels = [ + 'one' => trans_choice('custom-fields::custom-fields.record.announce_count', 1, ['count' => ':count']), + 'many' => trans_choice('custom-fields::custom-fields.record.announce_count', 2, ['count' => ':count']), + ]; + $initialRecords = $getRecordsByIds($selectedIds); + $initialOptions = $getInitialOptions(); + $pickerState = view('custom-fields::forms.partials.record-select-state', [ + 'applyStateBindingModifiers' => $applyStateBindingModifiers, + 'statePath' => $statePath, + 'key' => $key, + 'allowMultiple' => $allowMultiple, + 'maxValues' => $maxValues, + 'isDisabled' => $isDisabled, + 'initialRecords' => $initialRecords, + 'initialOptions' => $initialOptions, + 'maxVisiblePills' => $maxVisiblePills, + 'minSearchLength' => $minSearchLength, + 'shortSearchMessage' => $shortSearchMessage, + 'checksHolderConflicts' => $checksHolderConflicts, + 'confirmedStealIds' => $confirmedStealIds, + 'overflowLabels' => $overflowLabels, + 'countLabels' => $countLabels, + ])->render(); + + $chipClasses = 'fi-cf-record-chip inline-flex max-w-full items-center gap-1.5 rounded-md bg-gray-50 py-1 ps-1 pe-1.5 text-sm text-gray-950 ring-1 ring-gray-950/5 dark:bg-white/5 dark:text-white dark:ring-white/10'; +@endphp + + +
+
+ + + + + +
+
+ + + +
+ + + +
+ + + +
+ + @if (filled($createUrl)) + + + @endif +
+
+
diff --git a/resources/views/flavors/polished/relationship-configurator.blade.php b/resources/views/flavors/polished/relationship-configurator.blade.php new file mode 100644 index 00000000..4cb87523 --- /dev/null +++ b/resources/views/flavors/polished/relationship-configurator.blade.php @@ -0,0 +1,126 @@ +@php + use Illuminate\Support\Str; + + $fields = $getConfiguredFields(); + $sourceEntity = $getSourceEntity(); + $targetEntity = $getTargetEntity(); + $sentence = $getCardinalitySentence(); + $isSymmetric = $isSymmetric(); + $pairsAField = $pairsAField(); +@endphp + +
merge(['id' => $getId()], escape: false) + ->merge($getExtraAttributes(), escape: false) + ->class(['fi-sc-cf-relationship-configurator flex flex-col gap-4']) + }} +> +
+
+ +
+
+

+ {{ __('custom-fields::custom-fields.field.form.record.this_entity') }} +

+ +
+
+ +

+ {{ __('custom-fields::custom-fields.field.form.record.field_on_this_entity') }} +

+

{{ $getFieldName() }}

+
+ +
+ @if ($cardinality = ($fields['relationship.cardinality'] ?? null)) + {{ $cardinality }} + @endif + +

+ {{ $sentence ?? __('custom-fields::custom-fields.field.form.record.sentence_placeholder') }} +

+
+ +
+
+

+ {{ __('custom-fields::custom-fields.field.form.record.related_entity') }} +

+ + @if ($targetEntity !== null) +
+
+ @endif +
+ + @foreach (['relationship.target_entity_type', 'relationship.paired_field_name', 'relationship.paired_section_id'] as $name) + @if ($field = ($fields[$name] ?? null)) + {{ $field }} + @endif + @endforeach +
+
+ + @if ($symmetric = ($fields['relationship.is_symmetric'] ?? null)) +
+ {{ $symmetric }} +
+ @endif + + @if ($keepFirst = ($fields['relationship.keep_first'] ?? null)) +
+ {{ $keepFirst }} +
+ @endif +
diff --git a/resources/views/flavors/polished/type-picker.blade.php b/resources/views/flavors/polished/type-picker.blade.php new file mode 100644 index 00000000..13e67fba --- /dev/null +++ b/resources/views/flavors/polished/type-picker.blade.php @@ -0,0 +1,17 @@ +@php + $choices = $getTypeChoices(); + $statePath = $getStatePath(); + $isDisabled = $isDisabled(); +@endphp + + + @include('custom-fields::flavors.polished.partials.type-picker-grid', [ + 'choices' => $choices, + 'isDisabled' => $isDisabled, + 'label' => $getLabel(), + 'stateBinding' => $applyStateBindingModifiers("\$entangle('{$statePath}')"), + ]) + diff --git a/resources/views/forms/partials/record-move-buttons.blade.php b/resources/views/forms/partials/record-move-buttons.blade.php new file mode 100644 index 00000000..ce27133c --- /dev/null +++ b/resources/views/forms/partials/record-move-buttons.blade.php @@ -0,0 +1,29 @@ +{{-- The links are written in the order the chips are left in, so that order needs an + affordance a keyboard can reach. Shared by both flavors, like the state object. --}} + diff --git a/resources/views/forms/partials/record-select-state.blade.php b/resources/views/forms/partials/record-select-state.blade.php new file mode 100644 index 00000000..558aba39 --- /dev/null +++ b/resources/views/forms/partials/record-select-state.blade.php @@ -0,0 +1,481 @@ +{{-- The picker's behaviour, shared by every flavor: a flavor decides what the picker looks + like and never what it does, so this object is written once and included by both views. --}} +{ + state: $wire.{!! $applyStateBindingModifiers("\$entangle('{$statePath}')") !!}, + open: false, + search: '', + isSearching: false, + searchResults: [], + componentKey: @js($key), + allowMultiple: @js($allowMultiple), + maxValues: @js($maxValues), + isDisabled: @js($isDisabled), + recordsCache: @js($initialRecords), + initialOptions: @js(array_values($initialOptions)), + maxVisibleValues: @js($maxVisiblePills), + minSearchLength: @js($minSearchLength), + checksHolderConflicts: @js($checksHolderConflicts), + overflowLabels: @js($overflowLabels), + countLabels: @js($countLabels), + confirmedStealIds: @js($confirmedStealIds), + pendingSteal: null, + selectedSnapshot: [], + activeIndex: -1, + documentClickListener: null, + + init() { + this.commit(this.ids.filter(v => v || v === 0)); + + this.$watch('search', (value) => { + if (value.trim().length >= this.minSearchLength) { + this.performSearch(); + } else { + this.searchResults = []; + } + this.activeIndex = this.sortedOptions.length > 0 ? 0 : -1; + }); + + this.$watch('open', (isOpen) => { + if (isOpen) { + this.selectedSnapshot = [...this.ids]; + this.search = ''; + this.searchResults = []; + this.activeIndex = this.getInitialActiveIndex(); + this.$nextTick(() => { + this.$refs.searchInput?.focus(); + this.scrollActiveIntoView(); + }); + } else { + // When closing in multi-select, reorder state to match visual order + if (this.allowMultiple) { + const snapshotSelected = this.selectedSnapshot.filter(id => this.ids.includes(id)); + const newlySelected = this.ids.filter(id => !this.selectedSnapshot.includes(id)); + this.commit([...snapshotSelected, ...newlySelected]); + } + this.search = ''; + this.searchResults = []; + this.activeIndex = -1; + this.pendingSteal = null; + } + }); + + this.documentClickListener = (event) => { + if (this.open && !this.$el.contains(event.target)) { + this.close(); + } + }; + document.addEventListener('click', this.documentClickListener); + }, + + destroy() { + if (this.documentClickListener) { + document.removeEventListener('click', this.documentClickListener); + } + }, + + // The state is a list until a move is confirmed, and a map from then on, so every read + // goes through here and every write through commit(). + get ids() { + if (Array.isArray(this.state)) { + return this.state; + } + + return Array.isArray(this.state?.ids) ? this.state.ids : []; + }, + + // Each confirmation names the record it was given for, so it holds only while that + // record is in the payload and never answers for one added after it. + commit(ids) { + this.confirmedStealIds = this.confirmedStealIds.filter(id => ids.includes(id)); + + this.state = this.confirmedStealIds.length > 0 + ? { ids: ids, confirmed: [...this.confirmedStealIds] } + : ids; + }, + + // A count the reader can act on, in the plural form the locale picked server-side. + countLabel(labels, count) { + return (count === 1 ? labels.one : labels.many).replace(':count', count); + }, + + get activeDescendant() { + if (!this.open || this.activeIndex < 0 || this.activeIndex >= this.sortedOptions.length) { + return null; + } + return this.$id('option-' + this.activeIndex); + }, + + getInitialActiveIndex() { + if (!this.hasValues) return 0; + const firstSelectedIndex = this.sortedOptions.findIndex(opt => this.ids.includes(opt.id)); + return firstSelectedIndex >= 0 ? firstSelectedIndex : 0; + }, + + get canAddMore() { + if (!this.allowMultiple) { + return this.ids.length === 0; + } + return this.ids.length < this.maxValues; + }, + + get hasValues() { + return this.ids.length > 0; + }, + + get selectedRecords() { + // When the dropdown is open in multi-select, the snapshot keeps the visible order + // steady while selections change underneath it. + if (this.open && this.allowMultiple) { + const snapshotSelected = this.selectedSnapshot.filter(id => this.ids.includes(id)); + const newlySelected = this.ids.filter(id => !this.selectedSnapshot.includes(id)); + const orderedIds = [...snapshotSelected, ...newlySelected]; + return orderedIds.map(id => this.recordsCache[id] || { id, label: id, avatar: null }).filter(Boolean); + } + return this.ids.map(id => this.recordsCache[id] || { id, label: id, avatar: null }).filter(Boolean); + }, + + get visibleRecords() { + return this.selectedRecords.slice(0, this.maxVisibleValues); + }, + + get hiddenCount() { + return Math.max(0, this.selectedRecords.length - this.maxVisibleValues); + }, + + get sortedOptions() { + const searchLower = this.search.toLowerCase().trim(); + + if (searchLower.length >= this.minSearchLength && this.searchResults.length > 0) { + return this.sortBySelected([...this.searchResults]); + } + + let options = [...this.initialOptions]; + if (searchLower) { + options = options.filter(opt => opt.label.toLowerCase().includes(searchLower)); + } + + return this.sortBySelected(options); + }, + + sortBySelected(options) { + const selectedIds = this.allowMultiple ? this.selectedSnapshot : this.ids; + return options.sort((a, b) => { + const aSelected = selectedIds.includes(a.id); + const bSelected = selectedIds.includes(b.id); + if (aSelected && !bSelected) return -1; + if (!aSelected && bSelected) return 1; + if (aSelected && bSelected) { + return selectedIds.indexOf(a.id) - selectedIds.indexOf(b.id); + } + return 0; + }); + }, + + isSelected(recordId) { + return this.ids.includes(recordId); + }, + + get emptyStateMessage() { + const searchLength = this.search.trim().length; + if (searchLength >= this.minSearchLength) { + return @js(__('custom-fields::custom-fields.record.no_results')); + } + if (searchLength > 0) { + return @js($shortSearchMessage); + } + if (this.initialOptions.length === 0) { + return @js(__('custom-fields::custom-fields.record.none_available')); + } + return ''; + }, + + async performSearch() { + const query = this.search.trim(); + + if (query.length < this.minSearchLength) { + this.searchResults = []; + return; + } + + this.isSearching = true; + + try { + const results = await $wire.callSchemaComponentMethod( + this.componentKey, + 'getSearchResultsForJs', + { search: query } + ); + this.searchResults = Array.isArray(results) ? results : Object.values(results || {}); + } catch { + this.searchResults = []; + } finally { + this.isSearching = false; + } + }, + + // A record already held by someone else is confirmed before the writer resolves it, and + // the sentence shown is the one the writer would have refused with. + async holderConflictFor(recordId) { + if (!this.checksHolderConflicts || this.confirmedStealIds.includes(recordId)) { + return null; + } + + try { + return await $wire.callSchemaComponentMethod( + this.componentKey, + 'holderConflictFor', + { recordId: String(recordId) } + ); + } catch { + return null; + } + }, + + async confirmSteal() { + const pending = this.pendingSteal; + + if (!pending) return; + + this.confirmedStealIds = [...this.confirmedStealIds, pending.record.id]; + this.pendingSteal = null; + + await this.selectRecord(pending.record); + }, + + cancelSteal() { + this.pendingSteal = null; + }, + + toggle() { + if (this.isDisabled) return; + this.open ? this.close() : this.openPanel(); + }, + + openPanel() { + if (this.isDisabled || this.open) return; + this.$refs.panel?.open(this.$refs.trigger); + this.open = true; + }, + + close() { + if (!this.open) return; + this.$refs.panel?.close(); + this.open = false; + this.$refs.trigger?.focus(); + }, + + closePanel() { + this.close(); + }, + + onKeydown(event) { + if (this.isDisabled) return; + + switch (event.key) { + case 'ArrowDown': + event.preventDefault(); + event.stopPropagation(); + if (this.open) { + this.focusNext(); + } else { + this.openPanel(); + } + break; + case 'ArrowUp': + event.preventDefault(); + event.stopPropagation(); + if (this.open) { + this.focusPrevious(); + } else { + this.openPanel(); + } + break; + case 'Home': + if (this.open) { + event.preventDefault(); + this.focusFirst(); + } + break; + case 'End': + if (this.open) { + event.preventDefault(); + this.focusLast(); + } + break; + case 'Enter': + event.preventDefault(); + if (this.open && this.activeIndex >= 0 && this.activeIndex < this.sortedOptions.length) { + const record = this.sortedOptions[this.activeIndex]; + this.allowMultiple ? this.toggleRecord(record) : this.selectRecord(record); + } else if (!this.open) { + this.openPanel(); + } + break; + case ' ': + if (document.activeElement === this.$refs.searchInput) { + return; + } + if (!this.open) { + event.preventDefault(); + this.openPanel(); + } + break; + case 'Tab': + if (this.open) { + this.close(); + } + break; + } + }, + + onSearchKeydown(event) { + switch (event.key) { + case 'ArrowDown': + event.preventDefault(); + event.stopPropagation(); + this.focusNext(); + break; + case 'ArrowUp': + event.preventDefault(); + event.stopPropagation(); + this.focusPrevious(); + break; + case 'Home': + event.preventDefault(); + this.focusFirst(); + break; + case 'End': + event.preventDefault(); + this.focusLast(); + break; + case 'Enter': + event.preventDefault(); + event.stopPropagation(); + if (this.activeIndex >= 0 && this.activeIndex < this.sortedOptions.length) { + const record = this.sortedOptions[this.activeIndex]; + this.allowMultiple ? this.toggleRecord(record) : this.selectRecord(record); + } else if (this.sortedOptions.length > 0) { + const record = this.sortedOptions[0]; + this.allowMultiple ? this.toggleRecord(record) : this.selectRecord(record); + } + break; + case 'Escape': + event.preventDefault(); + event.stopPropagation(); + this.closePanel(); + break; + } + }, + + focusNext() { + const max = this.sortedOptions.length - 1; + if (max < 0) return; + this.activeIndex = this.activeIndex >= max ? 0 : this.activeIndex + 1; + this.scrollActiveIntoView(); + }, + + focusPrevious() { + const max = this.sortedOptions.length - 1; + if (max < 0) return; + this.activeIndex = this.activeIndex <= 0 ? max : this.activeIndex - 1; + this.scrollActiveIntoView(); + }, + + focusFirst() { + if (this.sortedOptions.length === 0) return; + this.activeIndex = 0; + this.scrollActiveIntoView(); + }, + + focusLast() { + if (this.sortedOptions.length === 0) return; + this.activeIndex = this.sortedOptions.length - 1; + this.scrollActiveIntoView(); + }, + + scrollActiveIntoView() { + this.$nextTick(() => { + const activeOption = this.$refs.optionsList?.querySelector('[data-highlighted]'); + if (activeOption) { + activeOption.scrollIntoView({ block: 'nearest' }); + } + }); + }, + + announceSelection(record, wasSelected) { + if (this.$refs.announcer) { + const action = wasSelected ? @js(__('custom-fields::custom-fields.record.announce_deselected')) : @js(__('custom-fields::custom-fields.record.announce_selected')); + let message = record.label + ' ' + action; + if (this.allowMultiple) { + message += '. ' + this.countLabel(this.countLabels, this.ids.length); + } + this.$refs.announcer.textContent = message; + } + }, + + // A selection can wait on the server's answer about the record's current holder, so the + // announcement is made once the selection has landed, never before it. + async toggleRecord(record) { + if (this.isSelected(record.id)) { + this.removeRecord(record.id); + this.announceSelection(record, true); + + return; + } + + await this.selectRecord(record); + + if (this.isSelected(record.id)) { + this.announceSelection(record, false); + } + }, + + async selectRecord(record) { + if (this.allowMultiple && !this.canAddMore) return; + + if (this.ids.includes(record.id)) return; + + const conflict = await this.holderConflictFor(record.id); + + if (conflict) { + this.pendingSteal = { record: record, message: conflict }; + return; + } + + this.recordsCache[record.id] = { + id: record.id, + label: record.label, + avatar: record.avatar, + avatarShape: record.avatarShape, + provenance: record.provenance ?? null + }; + + if (this.allowMultiple) { + this.commit([...this.ids, record.id]); + } else { + this.commit([record.id]); + this.closePanel(); + } + }, + + removeRecord(recordId) { + this.commit(this.ids.filter(id => id !== recordId)); + }, + + // The order the chips are left in is the order the links are written in, so the snapshot + // moves with the payload and the open panel keeps reading the same list. + moveRecord(recordId, offset) { + const ids = this.selectedRecords.map(record => record.id); + const from = ids.indexOf(recordId); + const to = from + offset; + + if (from < 0 || to < 0 || to >= ids.length) { + return; + } + + ids.splice(to, 0, ids.splice(from, 1)[0]); + + this.selectedSnapshot = [...ids]; + this.commit(ids); + } +} diff --git a/resources/views/forms/record-select-input.blade.php b/resources/views/forms/record-select-input.blade.php index af3b7863..45768059 100644 --- a/resources/views/forms/record-select-input.blade.php +++ b/resources/views/forms/record-select-input.blade.php @@ -7,16 +7,41 @@ $maxVisiblePills = $getMaxVisiblePills(); $addLabel = $getAddLabel(); $emptyStateLabel = $getEmptyStateLabel(); - $placeholder = $getPlaceholder() ?? __('Search records...'); + $placeholder = $getPlaceholder() ?? __('custom-fields::custom-fields.record.search_placeholder'); $key = $getKey(); $minSearchLength = $getMinSearchLength(); - $shortSearchMessage = __('Type at least :count characters to search', ['count' => $minSearchLength]); - - // Get initial records data for selected values + $shortSearchMessage = __('custom-fields::custom-fields.record.short_search', ['count' => $minSearchLength]); $state = $getState() ?? []; - $selectedIds = is_array($state) ? array_filter($state) : []; + $selectedIds = array_filter(is_array($state) ? ($state['ids'] ?? $state) : []); + // A pluralized key cannot be read by __(), so both forms are chosen server-side and the + // client picks between them by count. + $overflowLabels = [ + 'one' => trans_choice('custom-fields::custom-fields.record.more_records', 1, ['count' => ':count']), + 'many' => trans_choice('custom-fields::custom-fields.record.more_records', 2, ['count' => ':count']), + ]; + $countLabels = [ + 'one' => trans_choice('custom-fields::custom-fields.record.announce_count', 1, ['count' => ':count']), + 'many' => trans_choice('custom-fields::custom-fields.record.announce_count', 2, ['count' => ':count']), + ]; $initialRecords = $getRecordsByIds($selectedIds); $initialOptions = $getInitialOptions(); + $pickerState = view('custom-fields::forms.partials.record-select-state', [ + 'applyStateBindingModifiers' => $applyStateBindingModifiers, + 'statePath' => $statePath, + 'key' => $key, + 'allowMultiple' => $allowMultiple, + 'maxValues' => $maxValues, + 'isDisabled' => $isDisabled, + 'initialRecords' => $initialRecords, + 'initialOptions' => $initialOptions, + 'maxVisiblePills' => $maxVisiblePills, + 'minSearchLength' => $minSearchLength, + 'shortSearchMessage' => $shortSearchMessage, + 'checksHolderConflicts' => false, + 'confirmedStealIds' => [], + 'overflowLabels' => $overflowLabels, + 'countLabels' => $countLabels, + ])->render(); @endphp v && v !== ''); - - this.$watch('search', (value) => { - if (value.trim().length >= this.minSearchLength) { - this.performSearch(); - } else { - this.searchResults = []; - } - this.activeIndex = this.sortedOptions.length > 0 ? 0 : -1; - }); - - this.$watch('open', (isOpen) => { - if (isOpen) { - this.selectedSnapshot = [...this.state]; - this.search = ''; - this.searchResults = []; - this.activeIndex = this.getInitialActiveIndex(); - this.$nextTick(() => { - this.$refs.searchInput?.focus(); - this.scrollActiveIntoView(); - }); - } else { - // When closing in multi-select, reorder state to match visual order - if (this.allowMultiple) { - const snapshotSelected = this.selectedSnapshot.filter(id => this.state.includes(id)); - const newlySelected = this.state.filter(id => !this.selectedSnapshot.includes(id)); - this.state = [...snapshotSelected, ...newlySelected]; - } - this.search = ''; - this.searchResults = []; - this.activeIndex = -1; - } - }); - - this.documentClickListener = (event) => { - if (this.open && !this.$el.contains(event.target)) { - this.close(); - } - }; - document.addEventListener('click', this.documentClickListener); - }, - - destroy() { - if (this.documentClickListener) { - document.removeEventListener('click', this.documentClickListener); - } - }, - - get activeDescendant() { - if (!this.open || this.activeIndex < 0 || this.activeIndex >= this.sortedOptions.length) { - return null; - } - return this.$id('option-' + this.activeIndex); - }, - - getInitialActiveIndex() { - if (!this.hasValues) return 0; - // Find index of first selected item - const firstSelectedIndex = this.sortedOptions.findIndex(opt => this.state.includes(opt.id)); - return firstSelectedIndex >= 0 ? firstSelectedIndex : 0; - }, - - get canAddMore() { - if (!this.allowMultiple) { - return this.state.length === 0; - } - return this.state.length < this.maxValues; - }, - - get hasValues() { - return this.state.length > 0; - }, - - get selectedRecords() { - // When dropdown is open in multi-select, use selectedSnapshot order for consistency - // Items from snapshot that are still selected come first, then any newly selected items - if (this.open && this.allowMultiple) { - const snapshotSelected = this.selectedSnapshot.filter(id => this.state.includes(id)); - const newlySelected = this.state.filter(id => !this.selectedSnapshot.includes(id)); - const orderedIds = [...snapshotSelected, ...newlySelected]; - return orderedIds.map(id => this.recordsCache[id] || { id, label: id, avatar: null }).filter(Boolean); - } - return this.state.map(id => this.recordsCache[id] || { id, label: id, avatar: null }).filter(Boolean); - }, - - get visibleRecords() { - return this.selectedRecords.slice(0, this.maxVisibleValues); - }, - - get hiddenCount() { - return Math.max(0, this.selectedRecords.length - this.maxVisibleValues); - }, - - get sortedOptions() { - const searchLower = this.search.toLowerCase().trim(); - - // If searching (at or above the minimum) and have server results, use those - if (searchLower.length >= this.minSearchLength && this.searchResults.length > 0) { - return this.sortBySelected([...this.searchResults]); - } - - // Otherwise filter initial options client-side - let options = [...this.initialOptions]; - if (searchLower) { - options = options.filter(opt => opt.label.toLowerCase().includes(searchLower)); - } - - return this.sortBySelected(options); - }, - - sortBySelected(options) { - // In multi-select mode, use the snapshot to prevent reordering while dropdown is open - const selectedIds = this.allowMultiple ? this.selectedSnapshot : this.state; - return options.sort((a, b) => { - const aSelected = selectedIds.includes(a.id); - const bSelected = selectedIds.includes(b.id); - if (aSelected && !bSelected) return -1; - if (!aSelected && bSelected) return 1; - // Both selected: preserve selection order - if (aSelected && bSelected) { - return selectedIds.indexOf(a.id) - selectedIds.indexOf(b.id); - } - return 0; - }); - }, - - isSelected(recordId) { - return this.state.includes(recordId); - }, - - get emptyStateMessage() { - const searchLength = this.search.trim().length; - if (searchLength >= this.minSearchLength) { - return '{{ __('No records found') }}'; - } - if (searchLength > 0) { - return @js($shortSearchMessage); - } - if (this.initialOptions.length === 0) { - return '{{ __('No records available') }}'; - } - return ''; - }, - - async performSearch() { - const query = this.search.trim(); - - if (query.length < this.minSearchLength) { - this.searchResults = []; - return; - } - - this.isSearching = true; - - try { - const results = await $wire.callSchemaComponentMethod( - this.componentKey, - 'getSearchResultsForJs', - { search: query } - ); - this.searchResults = Array.isArray(results) ? results : Object.values(results || {}); - } catch { - this.searchResults = []; - } finally { - this.isSearching = false; - } - }, - - toggle() { - if (this.isDisabled) return; - this.open ? this.close() : this.openPanel(); - }, - - openPanel() { - if (this.isDisabled || this.open) return; - this.$refs.panel?.open(this.$refs.trigger); - this.open = true; - }, - - close() { - if (!this.open) return; - this.$refs.panel?.close(); - this.open = false; - this.$refs.trigger?.focus(); - }, - - closePanel() { - this.close(); - }, - - onKeydown(event) { - if (this.isDisabled) return; - - switch (event.key) { - case 'ArrowDown': - event.preventDefault(); - event.stopPropagation(); - if (this.open) { - this.focusNext(); - } else { - this.openPanel(); - } - break; - case 'ArrowUp': - event.preventDefault(); - event.stopPropagation(); - if (this.open) { - this.focusPrevious(); - } else { - this.openPanel(); - } - break; - case 'Home': - if (this.open) { - event.preventDefault(); - this.focusFirst(); - } - break; - case 'End': - if (this.open) { - event.preventDefault(); - this.focusLast(); - } - break; - case 'Enter': - event.preventDefault(); - if (this.open && this.activeIndex >= 0 && this.activeIndex < this.sortedOptions.length) { - const record = this.sortedOptions[this.activeIndex]; - this.allowMultiple ? this.toggleRecord(record) : this.selectRecord(record); - } else if (!this.open) { - this.openPanel(); - } - break; - case ' ': - if (document.activeElement === this.$refs.searchInput) { - return; - } - if (!this.open) { - event.preventDefault(); - this.openPanel(); - } - break; - case 'Tab': - if (this.open) { - this.close(); - } - break; - } - }, - - onSearchKeydown(event) { - switch (event.key) { - case 'ArrowDown': - event.preventDefault(); - event.stopPropagation(); - this.focusNext(); - break; - case 'ArrowUp': - event.preventDefault(); - event.stopPropagation(); - this.focusPrevious(); - break; - case 'Home': - event.preventDefault(); - this.focusFirst(); - break; - case 'End': - event.preventDefault(); - this.focusLast(); - break; - case 'Enter': - event.preventDefault(); - event.stopPropagation(); - if (this.activeIndex >= 0 && this.activeIndex < this.sortedOptions.length) { - const record = this.sortedOptions[this.activeIndex]; - this.allowMultiple ? this.toggleRecord(record) : this.selectRecord(record); - } else if (this.sortedOptions.length > 0) { - const record = this.sortedOptions[0]; - this.allowMultiple ? this.toggleRecord(record) : this.selectRecord(record); - } - break; - case 'Escape': - event.preventDefault(); - event.stopPropagation(); - this.closePanel(); - break; - } - }, - - focusNext() { - const max = this.sortedOptions.length - 1; - if (max < 0) return; - this.activeIndex = this.activeIndex >= max ? 0 : this.activeIndex + 1; - this.scrollActiveIntoView(); - }, - - focusPrevious() { - const max = this.sortedOptions.length - 1; - if (max < 0) return; - this.activeIndex = this.activeIndex <= 0 ? max : this.activeIndex - 1; - this.scrollActiveIntoView(); - }, - - focusFirst() { - if (this.sortedOptions.length === 0) return; - this.activeIndex = 0; - this.scrollActiveIntoView(); - }, - - focusLast() { - if (this.sortedOptions.length === 0) return; - this.activeIndex = this.sortedOptions.length - 1; - this.scrollActiveIntoView(); - }, - - scrollActiveIntoView() { - this.$nextTick(() => { - const activeOption = this.$refs.optionsList?.querySelector('[data-highlighted]'); - if (activeOption) { - activeOption.scrollIntoView({ block: 'nearest' }); - } - }); - }, - - announceSelection(record, wasSelected) { - if (this.$refs.announcer) { - const action = wasSelected ? 'deselected' : 'selected'; - let message = record.label + ' ' + action; - if (this.allowMultiple) { - message += `. ${this.state.length} item${this.state.length !== 1 ? 's' : ''} total`; - } - this.$refs.announcer.textContent = message; - } - }, - - toggleRecord(record) { - const wasSelected = this.isSelected(record.id); - if (wasSelected) { - this.removeRecord(record.id); - } else { - this.selectRecord(record); - } - this.announceSelection(record, wasSelected); - }, - - selectRecord(record) { - // In multi-select mode, check if we can add more - if (this.allowMultiple && !this.canAddMore) return; - - if (this.state.includes(record.id)) return; - - this.recordsCache[record.id] = { - id: record.id, - label: record.label, - avatar: record.avatar, - avatarShape: record.avatarShape - }; - - if (this.allowMultiple) { - // Use spread for proper reactivity - this.state = [...this.state, record.id]; - } else { - // Single-select: replace the current value - this.state = [record.id]; - this.closePanel(); - } - }, - - removeRecord(recordId) { - this.state = this.state.filter(id => id !== recordId); - } - }" + x-data="{!! $pickerState !!}" x-on:click.outside="close()" x-on:keydown.esc="open && (close(), $event.stopPropagation())" x-on:keydown="onKeydown($event)" @@ -480,7 +112,7 @@ class="h-5 w-5 object-cover shrink-0"
+
diff --git a/resources/views/forms/relationship-picker.blade.php b/resources/views/forms/relationship-picker.blade.php new file mode 100644 index 00000000..1d29f723 --- /dev/null +++ b/resources/views/forms/relationship-picker.blade.php @@ -0,0 +1,337 @@ +@php + use Relaticle\CustomFields\Data\RecordLinkPayload; + + $fieldWrapperView = $getFieldWrapperView(); + $isDisabled = $isDisabled(); + $statePath = $getStatePath(); + $allowMultiple = $getAllowMultiple(); + $maxValues = $getMaxValues(); + $maxVisiblePills = $getMaxVisiblePills(); + $addLabel = $getAddLabel(); + $emptyStateLabel = $getEmptyStateLabel(); + $placeholder = $getPlaceholder() ?? __('custom-fields::custom-fields.record.search_placeholder'); + $key = $getKey(); + $minSearchLength = $getMinSearchLength(); + $shortSearchMessage = __('custom-fields::custom-fields.record.short_search', ['count' => $minSearchLength]); + $checksHolderConflicts = $checksHolderConflicts(); + $createUrl = $getCreateUrl(); + $createLabel = $getCreateLabel(); + + // A confirmed move travels as a map naming the record it was given for, so a failed + // validation round trip brings back that record and not whichever one sorts first. + $state = $getState() ?? []; + $selectedIds = array_filter(is_array($state) ? ($state['ids'] ?? $state) : []); + $confirmedStealIds = RecordLinkPayload::confirmedIds(is_array($state) ? $state : [], $selectedIds); + // A pluralized key cannot be read by __(), so both forms are chosen server-side and the + // client picks between them by count. + $overflowLabels = [ + 'one' => trans_choice('custom-fields::custom-fields.record.more_records', 1, ['count' => ':count']), + 'many' => trans_choice('custom-fields::custom-fields.record.more_records', 2, ['count' => ':count']), + ]; + $countLabels = [ + 'one' => trans_choice('custom-fields::custom-fields.record.announce_count', 1, ['count' => ':count']), + 'many' => trans_choice('custom-fields::custom-fields.record.announce_count', 2, ['count' => ':count']), + ]; + $initialRecords = $getRecordsByIds($selectedIds); + $initialOptions = $getInitialOptions(); + $pickerState = view('custom-fields::forms.partials.record-select-state', [ + 'applyStateBindingModifiers' => $applyStateBindingModifiers, + 'statePath' => $statePath, + 'key' => $key, + 'allowMultiple' => $allowMultiple, + 'maxValues' => $maxValues, + 'isDisabled' => $isDisabled, + 'initialRecords' => $initialRecords, + 'initialOptions' => $initialOptions, + 'maxVisiblePills' => $maxVisiblePills, + 'minSearchLength' => $minSearchLength, + 'shortSearchMessage' => $shortSearchMessage, + 'checksHolderConflicts' => $checksHolderConflicts, + 'confirmedStealIds' => $confirmedStealIds, + 'overflowLabels' => $overflowLabels, + 'countLabels' => $countLabels, + ])->render(); +@endphp + + +
+ {{-- Hidden live region for screen reader announcements --}} +
+ + + {{-- Single Value Mode --}} + + + {{-- Multiple Values Mode --}} + + + + {{-- Dropdown Panel --}} +
+ {{-- Search Input --}} +
+ + + +
+ + {{-- A record another holder already has moves only on confirmation --}} + + + {{-- Options List --}} +
+ + + +
+ + @if (filled($createUrl)) + + + @endif +
+
+
diff --git a/resources/views/infolists/record-entry.blade.php b/resources/views/infolists/record-entry.blade.php index 13069178..0980049a 100644 --- a/resources/views/infolists/record-entry.blade.php +++ b/resources/views/infolists/record-entry.blade.php @@ -1,31 +1,35 @@ @php $state = $getState(); $records = $state['records'] ?? []; - $multiple = $state['multiple'] ?? false; + $chipsView = $state['chipsView'] ?? null; @endphp -
- @forelse ($records as $record) - @if ($record['url']) - - @if ($record['avatarUrl']) - - @endif - {{ $record['name'] }} - - @else -
- @if ($record['avatarUrl']) - - @endif - {{ $record['name'] }} -
- @endif - @empty - — - @endforelse -
+ @if ($chipsView !== null) + @include($chipsView, ['chips' => $records, 'maxVisible' => count($records)]) + @else +
+ @forelse ($records as $record) + @if ($record['url']) + + @if ($record['avatarUrl']) + + @endif + {{ $record['name'] }} + + @else +
+ @if ($record['avatarUrl']) + + @endif + {{ $record['name'] }} +
+ @endif + @empty + {{ __('custom-fields::custom-fields.record.no_records') }} + @endforelse +
+ @endif
diff --git a/resources/views/tables/columns/record-column.blade.php b/resources/views/tables/columns/record-column.blade.php index a8f94b76..bb97d7b5 100644 --- a/resources/views/tables/columns/record-column.blade.php +++ b/resources/views/tables/columns/record-column.blade.php @@ -5,8 +5,12 @@ $maxVisible = 1; $visibleRecords = array_slice($records, 0, $maxVisible); $hiddenCount = max(0, count($records) - $maxVisible); + $chipsView = $getChipsView(); @endphp +@if ($chipsView !== null) + @include($chipsView, ['chips' => $records, 'maxVisible' => $maxVisible]) +@else
+@endif diff --git a/src/Collections/FieldTypeCollection.php b/src/Collections/FieldTypeCollection.php index 5b71f0ec..691e471a 100644 --- a/src/Collections/FieldTypeCollection.php +++ b/src/Collections/FieldTypeCollection.php @@ -7,6 +7,9 @@ use Illuminate\Support\Collection; use Relaticle\CustomFields\Data\FieldTypeData; +/** + * @extends Collection + */ final class FieldTypeCollection extends Collection { public function acceptsArbitraryValues(): static diff --git a/src/Concerns/InteractsWithCustomFields.php b/src/Concerns/InteractsWithCustomFields.php index 21ad5116..5a2d4e32 100644 --- a/src/Concerns/InteractsWithCustomFields.php +++ b/src/Concerns/InteractsWithCustomFields.php @@ -25,7 +25,7 @@ public function table(Table $table): Table return $table ->modifyQueryUsing(function (Builder $query): void { - $query->with('customFieldValues.customField'); + $query->with('customFieldValues.customField')->withActiveCustomFieldLinks(); }) ->deferFilters(false) ->pushColumns($columns) diff --git a/src/Console/Commands/CleanupOrphanedValuesCommand.php b/src/Console/Commands/CleanupOrphanedValuesCommand.php index 019630d6..0df8de84 100644 --- a/src/Console/Commands/CleanupOrphanedValuesCommand.php +++ b/src/Console/Commands/CleanupOrphanedValuesCommand.php @@ -7,6 +7,7 @@ use Illuminate\Console\Command; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Support\Facades\DB; use Relaticle\CustomFields\CustomFields; @@ -61,7 +62,7 @@ public function handle(): int $orphanedCount = DB::table($table) ->where('entity_type', $type) - ->whereNotExists(function ($query) use ($entityTable): void { + ->whereNotExists(function (QueryBuilder $query) use ($entityTable): void { $query->select(DB::raw(1)) ->from($entityTable) ->whereColumn($entityTable.'.id', 'custom_field_values.entity_id'); @@ -107,7 +108,7 @@ public function handle(): int $count = DB::table($table) ->where('entity_type', $type) - ->whereNotExists(function ($query) use ($entityTable): void { + ->whereNotExists(function (QueryBuilder $query) use ($entityTable): void { $query->select(DB::raw(1)) ->from($entityTable) ->whereColumn($entityTable.'.id', 'custom_field_values.entity_id'); diff --git a/src/Console/Commands/MakeCustomFieldsMigrationCommand.php b/src/Console/Commands/MakeCustomFieldsMigrationCommand.php index 0ea59693..4adcbb5b 100644 --- a/src/Console/Commands/MakeCustomFieldsMigrationCommand.php +++ b/src/Console/Commands/MakeCustomFieldsMigrationCommand.php @@ -11,7 +11,7 @@ * ABOUTME: Artisan command to generate custom fields migration files * ABOUTME: Creates migration stubs in database/custom-fields directory for preset custom fields */ -class MakeCustomFieldsMigrationCommand extends GeneratorCommand +final class MakeCustomFieldsMigrationCommand extends GeneratorCommand { /** * The name and signature of the console command. @@ -50,7 +50,7 @@ protected function getPath($name): string // @pest-ignore-type /** * Get the date prefix for the migration. */ - protected function getDatePrefix(): string + private function getDatePrefix(): string { return date('Y_m_d_His'); } diff --git a/src/Console/Commands/MakeFieldTypeCommand.php b/src/Console/Commands/MakeFieldTypeCommand.php index e3761a7a..f3b14f93 100644 --- a/src/Console/Commands/MakeFieldTypeCommand.php +++ b/src/Console/Commands/MakeFieldTypeCommand.php @@ -10,7 +10,7 @@ use function Laravel\Prompts\select; -class MakeFieldTypeCommand extends GeneratorCommand +final class MakeFieldTypeCommand extends GeneratorCommand { /** * The name and signature of the console command. @@ -105,7 +105,7 @@ protected function buildClass($name): string // @pest-ignore-type /** * Get the class name from the full name. */ - protected function getClassName(string $name): string + private function getClassName(string $name): string { $className = class_basename($name); @@ -120,7 +120,7 @@ protected function getClassName(string $name): string /** * Get the field type name (without "FieldType" suffix). */ - protected function getFieldTypeName(string $name): string + private function getFieldTypeName(string $name): string { $className = class_basename($name); @@ -130,7 +130,7 @@ protected function getFieldTypeName(string $name): string /** * Get the data type for the field type. */ - protected function getDataType(): FieldDataType + private function getDataType(): FieldDataType { $typeOption = $this->option('type'); @@ -165,7 +165,7 @@ protected function getDataType(): FieldDataType /** * Get the appropriate configurator method for the given data type. */ - protected function getConfiguratorForDataType(FieldDataType $dataType): string + private function getConfiguratorForDataType(FieldDataType $dataType): string { return match ($dataType) { FieldDataType::STRING => 'text()', @@ -184,7 +184,7 @@ protected function getConfiguratorForDataType(FieldDataType $dataType): string /** * Get the appropriate form component import for the given data type. */ - protected function getFormComponentImport(FieldDataType $dataType): string + private function getFormComponentImport(FieldDataType $dataType): string { return match ($dataType) { FieldDataType::STRING => 'use Filament\Forms\Components\TextInput;', @@ -203,7 +203,7 @@ protected function getFormComponentImport(FieldDataType $dataType): string /** * Get the appropriate form component code for the given data type. */ - protected function getFormComponent(FieldDataType $dataType): string + private function getFormComponent(FieldDataType $dataType): string { return match ($dataType) { FieldDataType::STRING => 'return TextInput::make($customField->getFieldName()) @@ -251,7 +251,7 @@ protected function getFormComponent(FieldDataType $dataType): string /** * Check if the field type should use withoutUserOptions(). */ - protected function shouldUseWithoutUserOptions(FieldDataType $dataType): bool + private function shouldUseWithoutUserOptions(FieldDataType $dataType): bool { return $dataType === FieldDataType::SINGLE_CHOICE; } @@ -259,7 +259,7 @@ protected function shouldUseWithoutUserOptions(FieldDataType $dataType): bool /** * Get comment for choice field types explaining the behavior. */ - protected function getChoiceFieldComment(FieldDataType $dataType): string + private function getChoiceFieldComment(FieldDataType $dataType): string { return match ($dataType) { FieldDataType::SINGLE_CHOICE => '// withoutUserOptions() showcases built-in options - can be used with both single and multi choice', diff --git a/src/Console/Commands/Upgrade/Steps/CleanMultiValueValidationRulesStep.php b/src/Console/Commands/Upgrade/Steps/CleanMultiValueValidationRulesStep.php deleted file mode 100644 index 47c4a09f..00000000 --- a/src/Console/Commands/Upgrade/Steps/CleanMultiValueValidationRulesStep.php +++ /dev/null @@ -1,108 +0,0 @@ - */ - private const MULTI_VALUE_FIELD_TYPES = ['link', 'email', 'phone']; - - /** @var list */ - private const STRING_ONLY_RULES = [ - 'starts_with', - 'ends_with', - 'doesnt_start_with', - 'doesnt_end_with', - 'url', - 'email', - 'string', - 'alpha', - 'alpha_num', - 'alpha_dash', - 'regex', - 'not_regex', - 'active_url', - 'ascii', - 'ip', - 'ipv4', - 'ipv6', - 'json', - 'mac_address', - 'uuid', - 'uppercase', - ]; - - public function name(): string - { - return 'Clean Multi-Value Validation Rules'; - } - - public function description(): string - { - return 'Remove string-only validation rules (starts_with, url, etc.) from multi-value fields'; - } - - public function execute(bool $dryRun, Command $command): UpgradeStepResult - { - $fieldModel = CustomFields::newCustomFieldModel(); - - $affectedFields = $fieldModel->newQuery() - ->whereIn('type', self::MULTI_VALUE_FIELD_TYPES) - ->whereNotNull('validation_rules') - ->get(); - - $processed = 0; - $failed = 0; - - foreach ($affectedFields as $field) { - $rules = $field->validation_rules ?? collect(); - - $invalidKeys = $rules->keys()->intersect(self::STRING_ONLY_RULES); - - if ($invalidKeys->isEmpty()) { - continue; - } - - $ruleNames = $invalidKeys->implode(', '); - $command->line(sprintf(" Processing field '%s' (type: %s, id: %s): removing [%s]", $field->name, $field->type, $field->id, $ruleNames)); - - if (! $dryRun) { - $cleanedRules = $rules->except(self::STRING_ONLY_RULES)->toArray(); - - try { - $field->update([ - 'validation_rules' => $cleanedRules === [] ? null : $cleanedRules, - ]); - $processed++; - } catch (Throwable $e) { - $command->line(sprintf(' Failed to update field %s: %s', $field->id, $e->getMessage())); - $failed++; - } - } else { - $processed++; - } - } - - if ($processed === 0 && $failed === 0) { - $command->line(' No fields need cleanup'); - - return UpgradeStepResult::skipped('No multi-value fields with string-only validation rules found'); - } - - return UpgradeStepResult::success($processed, $failed); - } -} diff --git a/src/Console/Commands/Upgrade/Steps/MigrateEmailFormatStep.php b/src/Console/Commands/Upgrade/Steps/MigrateEmailFormatStep.php deleted file mode 100644 index 01037001..00000000 --- a/src/Console/Commands/Upgrade/Steps/MigrateEmailFormatStep.php +++ /dev/null @@ -1,21 +0,0 @@ - */ - private const LEGACY_LOOKUP_TYPES = ['select', 'multi-select', 'radio', 'checkbox-list', 'tags-input', 'toggle-buttons']; - - /** @var list */ - private const MULTI_VALUE_TYPES = ['multi-select', 'checkbox-list', 'tags-input']; - - public function name(): string - { - return 'Migrate Lookup Fields'; - } - - public function description(): string - { - return 'Convert lookup-based fields (select, multi-select, radio, etc.) to Record field type'; - } - - public function execute(bool $dryRun, Command $command): UpgradeStepResult - { - $customFieldModel = CustomFields::newCustomFieldModel(); - - $fieldsToMigrate = $customFieldModel::query() - ->withoutGlobalScopes() - ->whereIn('type', self::LEGACY_LOOKUP_TYPES) - ->whereNotNull('lookup_type') - ->where('lookup_type', '!=', '') - ->get(); - - if ($fieldsToMigrate->isEmpty()) { - return UpgradeStepResult::skipped('No legacy lookup fields found'); - } - - $command->table( - ['ID', 'Name', 'Current Type', 'Lookup Type', 'Will Become'], - $fieldsToMigrate->map(fn (CustomField $field): array => [ - $field->getKey(), - $field->name, - $field->type, - $field->lookup_type, - 'record'.(in_array($field->type, self::MULTI_VALUE_TYPES, true) ? ' (multi)' : ' (single)'), - ])->toArray() - ); - - if ($dryRun) { - return UpgradeStepResult::success($fieldsToMigrate->count()); - } - - $migrated = 0; - $failed = 0; - $errors = []; - - foreach ($fieldsToMigrate as $field) { - try { - $originalType = $field->type; - $isMultiValue = in_array($originalType, self::MULTI_VALUE_TYPES, true); - - $field->type = 'record'; - - $settings = $field->settings instanceof CustomFieldSettingsData - ? $field->settings - : new CustomFieldSettingsData; - - $settings->allow_multiple = $isMultiValue; - $field->settings = $settings; - - $field->saveQuietly(); - - $command->line(sprintf( - ' āœ“ %s: %s → record%s', - $field->name, - $originalType, - $isMultiValue ? ' (allow_multiple=true)' : '' - )); - - $migrated++; - } catch (Throwable $e) { - $command->line(sprintf(' āœ— %s: %s', $field->name, $e->getMessage())); - $errors[] = sprintf('%s: %s', $field->name, $e->getMessage()); - $failed++; - } - } - - return new UpgradeStepResult( - success: $failed === 0, - itemsProcessed: $migrated, - itemsFailed: $failed, - errors: $errors, - ); - } -} diff --git a/src/Console/Commands/Upgrade/Steps/MigratePhoneFormatStep.php b/src/Console/Commands/Upgrade/Steps/MigratePhoneFormatStep.php deleted file mode 100644 index 4d0d948a..00000000 --- a/src/Console/Commands/Upgrade/Steps/MigratePhoneFormatStep.php +++ /dev/null @@ -1,21 +0,0 @@ -table('custom_field_relationships')) || ! Schema::hasTable($this->table('custom_field_links'))) { + return UpgradeStepResult::skipped(sprintf( + 'Tables %s and %s do not exist yet: publish and run the relationship migrations first.', + $this->table('custom_field_relationships'), + $this->table('custom_field_links'), + )); + } + + $created = 0; + $failed = 0; + $warnings = []; + + foreach ($this->records->recordFields() as $field) { + $command->line(sprintf(' Migrating record field %s...', $field->code)); + + $definition = $this->records->definitionFor($field); + + if (! $definition instanceof CustomFieldRelationship && blank($this->records->legacyTargetEntityType($field))) { + // A field that never stored a link has nothing to lose by having no target, + // so only one holding values stops the upgrade. + if (! $this->records->values($field)->exists()) { + $warnings[] = sprintf("Field '%s' has no lookup type and no values, so there is nothing to migrate", $field->code); + $command->line(sprintf(' ā—‹ %s: no lookup type and no values, skipped', $field->code)); + + continue; + } + + $failed++; + $warnings[] = sprintf("Field '%s' holds values but has no lookup type, so its target is unknown", $field->code); + $command->line(sprintf(' āœ— %s: values with no lookup type', $field->code)); + + continue; + } + + // Legacy values are always written from the record that holds the field, so a + // field reading the far end of its definition would migrate to reversed edges. + if ($definition instanceof CustomFieldRelationship && (string) $definition->from_field_id !== (string) $field->getKey()) { + $failed++; + $warnings[] = sprintf("Field '%s' reads the to end of relationship '%s', so its value rows need migrating by hand", $field->code, $definition->code); + $command->line(sprintf(' ā—‹ %s: reads the to end of %s, skipped', $field->code, $definition->code)); + + continue; + } + + $dangling = 0; + + $links = $dryRun + ? $this->countLinks($field, $definition, $dangling) + : $this->migrate($field, $definition, $dangling); + + $created += $links; + $command->line(sprintf(' āœ“ %s: %d link(s)%s', $field->code, $links, $dryRun ? ' would be created' : ' created')); + + if ($dangling > 0) { + $warnings[] = sprintf("Field '%s': %d id(s) point at rows that no longer exist and were skipped", $field->code, $dangling); + $command->line(sprintf(' ā—‹ %s: %d id(s) point at missing rows, skipped', $field->code, $dangling)); + } + } + + return new UpgradeStepResult( + success: $failed === 0, + itemsProcessed: $created, + itemsFailed: $failed, + warnings: $warnings, + ); + } + + private function migrate(CustomField $field, ?CustomFieldRelationship $definition, int &$dangling): int + { + $created = 0; + $skipped = 0; + + DB::transaction(function () use ($field, $definition, &$created, &$skipped): void { + $definition ??= $this->createDefinition($field); + + $this->records->values($field)->chunkById(self::CHUNK_SIZE, function (EloquentCollection $values) use ($definition, $field, &$created, &$skipped): void { + $ledger = $this->records->ledgerTargets($definition, $values); + $reachable = $this->reachableTargets($definition->to_entity_type, $values); + + foreach ($values as $value) { + foreach ($this->records->targets($value) as $index => $targetId) { + if (in_array($targetId, $ledger[(string) $value->entity_id] ?? [], true)) { + continue; + } + + if (! in_array($targetId, $reachable, true)) { + $skipped++; + + continue; + } + + $this->insert($definition, $field, $value, $targetId, $index); + $ledger[(string) $value->entity_id][] = $targetId; + $created++; + } + } + }); + }); + + $dangling += $skipped; + + return $created; + } + + private function countLinks(CustomField $field, ?CustomFieldRelationship $definition, int &$dangling): int + { + $counted = 0; + $skipped = 0; + $targetType = $definition instanceof CustomFieldRelationship + ? $definition->to_entity_type + : $this->records->legacyTargetEntityType($field); + + $this->records->values($field)->chunkById(self::CHUNK_SIZE, function (EloquentCollection $values) use ($definition, $targetType, &$counted, &$skipped): void { + $ledger = $definition instanceof CustomFieldRelationship ? $this->records->ledgerTargets($definition, $values) : []; + $reachable = $this->reachableTargets($targetType, $values); + + foreach ($values as $value) { + foreach ($this->records->targets($value) as $targetId) { + if (in_array($targetId, $ledger[(string) $value->entity_id] ?? [], true)) { + continue; + } + + if (! in_array($targetId, $reachable, true)) { + $skipped++; + + continue; + } + + $counted++; + } + } + }); + + $dangling += $skipped; + + return $counted; + } + + /** + * A legacy array can still name a row somebody deleted outright, and an edge to nothing + * is the dangling reference the ledger exists to end. Global scopes come off the target + * query: one run migrates every tenant, and a soft-deleted end keeps its edges. + * + * @param EloquentCollection $values + * @return array + */ + private function reachableTargets(string $entityType, EloquentCollection $values): array + { + $ids = array_values(array_unique(array_merge(...array_map( + fn (CustomFieldValue $value): array => $this->records->targets($value), + $values->all(), + ) ?: [[]]))); + + if ($ids === []) { + return []; + } + + $entityClass = Relation::getMorphedModel($entityType) ?? $entityType; + + if (! class_exists($entityClass) || ! is_subclass_of($entityClass, Model::class)) { + return []; + } + + $target = new $entityClass; + + return $target->newQuery() + ->withoutGlobalScopes() + ->whereKey($ids) + ->pluck($target->getKeyName()) + ->map(static fn (mixed $key): string => (string) $key) + ->all(); + } + + private function insert(CustomFieldRelationship $definition, CustomField $field, CustomFieldValue $value, string $targetId, int $index): void + { + $attributes = [ + 'relationship_id' => $definition->getKey(), + 'from_entity_type' => $value->entity_type, + 'from_entity_id' => $value->entity_id, + 'to_entity_type' => $definition->to_entity_type, + 'to_entity_id' => $targetId, + 'sort_order' => $index, + 'active_from' => now(), + 'source' => CustomFieldLink::SOURCE_MIGRATION, + ]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $tenantKey = (string) config('custom-fields.database.column_names.tenant_foreign_key'); + $attributes[$tenantKey] = $field->{$tenantKey}; + } + + CustomFields::newLinkModel()->newQuery()->create($attributes); + } + + /** + * A 3.x record field points one way and its multiplicity lived in the settings, so that + * is the definition it becomes: one slot, no partner. The field keeps its record type, + * which is why a host reading this log sees no rename. + */ + private function createDefinition(CustomField $field): CustomFieldRelationship + { + $attributes = [ + 'code' => $this->availableCode($field), + 'from_entity_type' => $field->entity_type, + 'to_entity_type' => $this->records->legacyTargetEntityType($field), + 'cardinality' => $field->settings->allow_multiple + ? RelationshipCardinality::ManyToMany + : RelationshipCardinality::ManyToOne, + 'is_symmetric' => false, + 'from_field_id' => $field->getKey(), + 'to_field_id' => null, + ]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $tenantKey = (string) config('custom-fields.database.column_names.tenant_foreign_key'); + $attributes[$tenantKey] = $field->{$tenantKey}; + } + + return CustomFields::newRelationshipModel()->newQuery()->create($attributes); + } + + /** + * Field codes are unique per entity type and definition codes per tenant, so the same + * code can arrive twice from two entities of one tenant, and every tenant may hold its + * own copy of it. + */ + private function availableCode(CustomField $field): string + { + $candidate = $field->code; + $suffix = 1; + + while ($this->codeIsTaken($candidate, $field)) { + $candidate = sprintf('%s_%d', $field->code, $suffix); + $suffix++; + } + + return $candidate; + } + + private function codeIsTaken(string $code, CustomField $field): bool + { + $query = CustomFields::newRelationshipModel() + ->newQuery() + ->withoutGlobalScopes() + ->where('code', $code); + + if (! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + return $query->exists(); + } + + $tenantKey = (string) config('custom-fields.database.column_names.tenant_foreign_key'); + $tenantId = $field->{$tenantKey}; + + if ($tenantId === null) { + return $query->whereNull($tenantKey)->exists(); + } + + return $query->where($tenantKey, $tenantId)->exists(); + } + + private function table(string $key): string + { + return (string) config('custom-fields.database.table_names.'.$key); + } +} diff --git a/src/Console/Commands/Upgrade/Steps/MigrateStringToJsonFormatStep.php b/src/Console/Commands/Upgrade/Steps/MigrateStringToJsonFormatStep.php deleted file mode 100644 index 8ea2f853..00000000 --- a/src/Console/Commands/Upgrade/Steps/MigrateStringToJsonFormatStep.php +++ /dev/null @@ -1,94 +0,0 @@ -fieldTypeLabel()); - } - - public function description(): string - { - return sprintf('Convert %s field values from single string to array format', $this->fieldType()); - } - - public function execute(bool $dryRun, Command $command): UpgradeStepResult - { - $customFieldModel = CustomFields::newCustomFieldModel(); - $customFieldValueModel = CustomFields::newValueModel(); - $fieldType = $this->fieldType(); - - $fields = $customFieldModel::query() - ->withoutGlobalScopes() - ->where('type', $fieldType) - ->get(); - - if ($fields->isEmpty()) { - return UpgradeStepResult::skipped(sprintf('No %s fields found', $fieldType)); - } - - $valuesToMigrate = $customFieldValueModel::query() - ->withoutGlobalScopes() - ->whereIn('custom_field_id', $fields->pluck('id')) - ->whereNotNull('string_value') - ->where('string_value', '!=', '') - ->whereNull('json_value') - ->get(); - - if ($valuesToMigrate->isEmpty()) { - return UpgradeStepResult::skipped(sprintf('No legacy %s values found', $fieldType)); - } - - $command->line(sprintf(' Found %d %s value(s) to migrate', $valuesToMigrate->count(), $fieldType)); - - if ($dryRun) { - return UpgradeStepResult::success($valuesToMigrate->count()); - } - - $migrated = 0; - $failed = 0; - $errors = []; - - foreach ($valuesToMigrate as $value) { - try { - $originalValue = $value->string_value; - - $value->json_value = [$originalValue]; - $value->string_value = null; - $value->save(); - - $migrated++; - } catch (Throwable $e) { - $command->line(sprintf(' āœ— Value ID %s: %s', $value->id, $e->getMessage())); - $errors[] = sprintf('Value ID %s: %s', $value->id, $e->getMessage()); - $failed++; - } - } - - $command->line(sprintf(' āœ“ Migrated %d %s value(s)', $migrated, $fieldType)); - - return new UpgradeStepResult( - success: $failed === 0, - itemsProcessed: $migrated, - itemsFailed: $failed, - errors: $errors, - ); - } -} diff --git a/src/Console/Commands/Upgrade/Steps/MigrateValidationRulesFormatStep.php b/src/Console/Commands/Upgrade/Steps/MigrateValidationRulesFormatStep.php deleted file mode 100644 index 816efb26..00000000 --- a/src/Console/Commands/Upgrade/Steps/MigrateValidationRulesFormatStep.php +++ /dev/null @@ -1,369 +0,0 @@ - */ - private const TEXT_LIKE_TYPES = [ - 'text', 'textarea', 'markdown_editor', 'rich_editor', 'link', 'email', 'phone', - ]; - - /** @var list */ - private const NUMERIC_TYPES = ['number', 'currency']; - - /** @var list */ - private const MULTI_SELECT_TYPES = ['multi_select', 'checkbox_list', 'tags_input', 'record']; - - /** @var list */ - private const FILE_TYPES = ['file_upload']; - - public function name(): string - { - return 'Migrate Validation Rules Format'; - } - - public function description(): string - { - return 'Convert validation_rules from old array-of-objects format to new key-value format'; - } - - public function execute(bool $dryRun, Command $command): UpgradeStepResult - { - $fieldModel = CustomFields::newCustomFieldModel(); - - $fields = $fieldModel->newQuery() - ->withoutGlobalScopes() - ->whereNotNull('validation_rules') - ->get(); - - $processed = 0; - $failed = 0; - $warnings = []; - - foreach ($fields as $field) { - $rules = $field->validation_rules; - if (! $rules instanceof Collection) { - continue; - } - - if ($rules->isEmpty()) { - continue; - } - - if (! $this->isOldFormat($rules)) { - continue; - } - - $command->line(sprintf(" Processing field '%s' (type: %s, id: %s)", $field->name, $field->type, $field->id)); - - $fieldWarnings = []; - $newRules = $this->convertRules($rules, $field->type, $fieldWarnings); - - foreach ($fieldWarnings as $warning) { - $command->line(' Warning: '.$warning); - $warnings[] = sprintf("Field '%s' (id: %s): %s", $field->name, $field->id, $warning); - } - - if (! $dryRun) { - try { - $field->update([ - 'validation_rules' => $newRules->isEmpty() ? null : $newRules->toArray(), - ]); - $processed++; - } catch (Throwable $e) { - $command->line(sprintf(' Failed to update field %s: %s', $field->id, $e->getMessage())); - $failed++; - } - } else { - $processed++; - } - } - - if ($processed === 0 && $failed === 0) { - $command->line(' No fields need migration'); - - return UpgradeStepResult::skipped('No fields with old validation rules format found'); - } - - $result = UpgradeStepResult::success($processed, $failed); - - if ($warnings !== []) { - return new UpgradeStepResult( - success: $result->success, - itemsProcessed: $result->itemsProcessed, - itemsFailed: $result->itemsFailed, - warnings: $warnings, - ); - } - - return $result; - } - - private function isOldFormat(Collection $rules): bool - { - $firstItem = $rules->first(); - - return is_array($firstItem) && array_key_exists('name', $firstItem); - } - - /** @param list $warnings */ - private function convertRules(Collection $rules, string $fieldType, array &$warnings): Collection - { - $newRules = collect(); - $hasFileRule = $rules->contains(fn ($rule): bool => is_array($rule) && ($rule['name'] ?? '') === 'file'); - - foreach ($rules as $rule) { - if (! is_array($rule)) { - continue; - } - - if (! isset($rule['name'])) { - continue; - } - - $ruleName = $rule['name']; - $parameters = $rule['parameters'] ?? []; - $firstParam = $this->getFirstParameterValue($parameters); - - $converted = $this->convertRule($ruleName, $firstParam, $parameters, $fieldType, $hasFileRule, $warnings); - - if ($converted !== null) { - foreach ($converted as $key => $value) { - $newRules->put($key, $value); - } - } - } - - return $newRules; - } - - /** - * @param list $parameters - * @param list $warnings - * @return array|null - */ - private function convertRule( - string $ruleName, - ?string $firstParam, - array $parameters, - string $fieldType, - bool $hasFileRule, - array &$warnings, - ): ?array { - return match ($ruleName) { - 'required' => ['required' => true], - 'integer' => ['decimal_places' => 0], - 'file' => null, - 'min' => $this->convertMinRule($firstParam, $fieldType, $warnings), - 'max' => $this->convertMaxRule($firstParam, $fieldType, $hasFileRule, $warnings), - 'after', 'after_or_equal' => $this->convertDateMinRule($firstParam, $ruleName, $warnings), - 'before', 'before_or_equal' => $this->convertDateMaxRule($firstParam, $ruleName, $warnings), - 'decimal' => $this->convertDecimalRule($firstParam, $warnings), - 'mimes', 'mimetypes' => $this->convertMimesRule($parameters), - default => $this->warn($warnings, sprintf("Rule '%s' cannot be mapped to the new format, discarding", $ruleName)), - }; - } - - /** - * @param list $warnings - * @return array|null - */ - private function convertMinRule(?string $value, string $fieldType, array &$warnings): ?array - { - if ($value === null) { - $warnings[] = "Rule 'min' has no parameter value, skipping"; - - return null; - } - - if (in_array($fieldType, self::TEXT_LIKE_TYPES, true)) { - return ['min_length' => (int) $value]; - } - - if (in_array($fieldType, self::NUMERIC_TYPES, true)) { - return ['min_value' => (float) $value]; - } - - if (in_array($fieldType, self::MULTI_SELECT_TYPES, true)) { - return ['min_selections' => (int) $value]; - } - - $warnings[] = sprintf("Rule 'min' not applicable for field type '%s', discarding", $fieldType); - - return null; - } - - /** - * @param list $warnings - * @return array|null - */ - private function convertMaxRule(?string $value, string $fieldType, bool $hasFileRule, array &$warnings): ?array - { - if ($value === null) { - $warnings[] = "Rule 'max' has no parameter value, skipping"; - - return null; - } - - if (in_array($fieldType, self::FILE_TYPES, true) || $hasFileRule) { - return ['max_size_kb' => (int) $value]; - } - - if (in_array($fieldType, self::TEXT_LIKE_TYPES, true)) { - return ['max_length' => (int) $value]; - } - - if (in_array($fieldType, self::NUMERIC_TYPES, true)) { - return ['max_value' => (float) $value]; - } - - if (in_array($fieldType, self::MULTI_SELECT_TYPES, true)) { - return ['max_selections' => (int) $value]; - } - - $warnings[] = sprintf("Rule 'max' not applicable for field type '%s', discarding", $fieldType); - - return null; - } - - /** - * @param list $warnings - * @return array|null - */ - private function convertDateMinRule(?string $value, string $ruleName, array &$warnings): ?array - { - if ($value === null) { - $warnings[] = sprintf("Rule '%s' has no parameter value, skipping", $ruleName); - - return null; - } - - $constraint = $this->parseDateConstraint($value, $warnings); - - if ($constraint === null) { - return null; - } - - return ['min_date' => $constraint]; - } - - /** - * @param list $warnings - * @return array|null - */ - private function convertDateMaxRule(?string $value, string $ruleName, array &$warnings): ?array - { - if ($value === null) { - $warnings[] = sprintf("Rule '%s' has no parameter value, skipping", $ruleName); - - return null; - } - - $constraint = $this->parseDateConstraint($value, $warnings); - - if ($constraint === null) { - return null; - } - - return ['max_date' => $constraint]; - } - - /** - * @param list $warnings - * @return array{anchor: string, offset: int, offset_unit: string, offset_direction: string}|null - */ - private function parseDateConstraint(string $value, array &$warnings): ?array - { - return match ($value) { - 'today' => [ - 'anchor' => 'today', - 'offset' => 0, - 'offset_unit' => 'days', - 'offset_direction' => 'after', - ], - 'tomorrow' => [ - 'anchor' => 'today', - 'offset' => 1, - 'offset_unit' => 'days', - 'offset_direction' => 'after', - ], - 'yesterday' => [ - 'anchor' => 'today', - 'offset' => 1, - 'offset_unit' => 'days', - 'offset_direction' => 'before', - ], - default => $this->warn( - $warnings, - sprintf("Absolute date constraint '%s' cannot be automatically converted, discarding", $value), - ), - }; - } - - /** - * @param list $warnings - * @return array|null - */ - private function convertDecimalRule(?string $value, array &$warnings): ?array - { - if ($value === null) { - $warnings[] = "Rule 'decimal' has no parameter value, skipping"; - - return null; - } - - return ['decimal_places' => (int) $value]; - } - - /** - * @param list $parameters - * @return array> - */ - private function convertMimesRule(array $parameters): array - { - $types = array_map( - fn (array $param): string => $param['value'], - $parameters, - ); - - return ['accepted_types' => array_values(array_filter($types))]; - } - - /** - * @param list $warnings - */ - private function warn(array &$warnings, string $message): null - { - $warnings[] = $message; - - return null; - } - - /** @param list $parameters */ - private function getFirstParameterValue(array $parameters): ?string - { - if ($parameters === []) { - return null; - } - - $first = $parameters[0]; - - return is_array($first) ? $first['value'] : null; - } -} diff --git a/src/Console/Commands/Upgrade/Steps/PurgeMigratedRecordValuesStep.php b/src/Console/Commands/Upgrade/Steps/PurgeMigratedRecordValuesStep.php new file mode 100644 index 00000000..3a8b7bd4 --- /dev/null +++ b/src/Console/Commands/Upgrade/Steps/PurgeMigratedRecordValuesStep.php @@ -0,0 +1,100 @@ +unmigrated->codes(); + + if ($unmigrated !== []) { + return UpgradeStepResult::failed(sprintf( + 'Nothing is purged while %s still store links in json_value: run the Migrate Record Links step first.', + implode(', ', $unmigrated), + )); + } + + $purged = 0; + + foreach ($this->migratedFields() as $field) { + $command->line(sprintf(' Purging value rows of %s...', $field->code)); + + $values = $this->values($field); + $rows = $dryRun ? $values->count() : $values->delete(); + + $purged += $rows; + $command->line(sprintf(' āœ“ %s: %d row(s)%s', $field->code, $rows, $dryRun ? ' would be deleted' : ' deleted')); + } + + return UpgradeStepResult::success($purged); + } + + /** + * Every record field is definition-backed by now, the guard above having said so. + * + * @return array + */ + private function migratedFields(): array + { + $fieldsTable = (string) config('custom-fields.database.table_names.custom_fields'); + + if (! Schema::hasTable($fieldsTable)) { + return []; + } + + return CustomFields::newCustomFieldModel() + ->newQuery() + ->withoutGlobalScopes() + ->where('type', 'record') + ->orderBy('id') + ->get() + ->all(); + } + + /** + * @return Builder + */ + private function values(CustomField $field): Builder + { + return CustomFields::newValueModel() + ->newQuery() + ->withoutGlobalScopes() + ->where('custom_field_id', $field->getKey()) + ->whereNotNull('json_value'); + } +} diff --git a/src/Console/Commands/Upgrade/Steps/ValidateSchemaStep.php b/src/Console/Commands/Upgrade/Steps/ValidateSchemaStep.php index a4abd033..6ec97d64 100644 --- a/src/Console/Commands/Upgrade/Steps/ValidateSchemaStep.php +++ b/src/Console/Commands/Upgrade/Steps/ValidateSchemaStep.php @@ -6,19 +6,22 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\Schema; +use Relaticle\CustomFields\Console\Commands\Upgrade\UnmigratedRecordFields; use Relaticle\CustomFields\Console\Commands\Upgrade\UpgradeStep; use Relaticle\CustomFields\Console\Commands\Upgrade\UpgradeStepResult; +use Relaticle\CustomFields\Console\Commands\UpgradeCommand; +use Relaticle\CustomFields\Enums\CustomFieldsFeature; +use Relaticle\CustomFields\FeatureSystem\FeatureManager; -/** - * Validates database schema compatibility for v3. - */ final class ValidateSchemaStep implements UpgradeStep { + public function __construct(private readonly UnmigratedRecordFields $unmigrated) {} + /** @var array> */ private const REQUIRED_COLUMNS = [ 'custom_fields' => [ 'id', 'entity_type', 'name', 'code', 'type', - 'lookup_type', 'settings', 'sort_order', + 'settings', 'sort_order', ], 'custom_field_values' => [ 'id', 'entity_type', 'entity_id', 'custom_field_id', @@ -30,6 +33,19 @@ final class ValidateSchemaStep implements UpgradeStep ], ]; + /** @var array> */ + private const RELATIONSHIP_COLUMNS = [ + 'custom_field_relationships' => [ + 'id', 'code', 'from_entity_type', 'to_entity_type', 'cardinality', + 'from_field_id', 'to_field_id', 'is_symmetric', + ], + 'custom_field_links' => [ + 'id', 'relationship_id', 'from_entity_type', 'from_entity_id', + 'to_entity_type', 'to_entity_id', 'sort_order', 'active_from', + 'active_until', 'source', + ], + ]; + public function name(): string { return 'Validate Schema'; @@ -37,7 +53,7 @@ public function name(): string public function description(): string { - return 'Verify database schema compatibility for v3'; + return 'Verify the database schema before running upgrade steps'; } public function execute(bool $dryRun, Command $command): UpgradeStepResult @@ -46,7 +62,7 @@ public function execute(bool $dryRun, Command $command): UpgradeStepResult $warnings = []; $validated = 0; - foreach (self::REQUIRED_COLUMNS as $tableKey => $requiredColumns) { + foreach ($this->requiredColumns() as $tableKey => $requiredColumns) { $tableName = config('custom-fields.database.table_names.'.$tableKey, $tableKey); if (! Schema::hasTable($tableName)) { @@ -86,6 +102,8 @@ public function execute(bool $dryRun, Command $command): UpgradeStepResult $command->line(sprintf(' ā—‹ Table %s: not found (optional)', $sectionsTable)); } + $this->reportUnmigratedRecordFields($command, $errors, $warnings); + if ($errors !== []) { return new UpgradeStepResult( success: false, @@ -102,4 +120,55 @@ public function execute(bool $dryRun, Command $command): UpgradeStepResult warnings: $warnings, ); } + + /** + * A host with the relationships feature on has the two tables, or it cannot store a + * single edge. With the feature off they are never migrated, so they are never required. + * + * @return array> + */ + private function requiredColumns(): array + { + if (! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_RELATIONSHIPS)) { + return self::REQUIRED_COLUMNS; + } + + return [...self::REQUIRED_COLUMNS, ...self::RELATIONSHIP_COLUMNS]; + } + + /** + * Record links left in json_value are invisible to 4.x, which reads the ledger. It is a + * warning while this run still migrates them, and the wall otherwise. + * + * @param array $errors + * @param array $warnings + */ + private function reportUnmigratedRecordFields(Command $command, array &$errors, array &$warnings): void + { + if (! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_RELATIONSHIPS)) { + return; + } + + $codes = $this->unmigrated->codes(); + + if ($codes === []) { + return; + } + + $fields = implode(', ', $codes); + + if ($errors === [] && $command instanceof UpgradeCommand && $command->willRun(UpgradeCommand::STEP_MIGRATE_RECORD_LINKS)) { + $warnings[] = sprintf('%s still store record links in json_value; the Migrate Record Links step moves them', $fields); + $command->line(sprintf(' ā—‹ %s: links still in json_value, migrating below', $fields)); + + return; + } + + $errors[] = sprintf( + '%s still store record links in json_value with no relationship definition. Run custom-fields:upgrade with the %s step after publishing the relationship migrations.', + $fields, + UpgradeCommand::STEP_MIGRATE_RECORD_LINKS, + ); + $command->line(sprintf(' āœ— %s: record links still in json_value', $fields)); + } } diff --git a/src/Console/Commands/Upgrade/UnmigratedRecordFields.php b/src/Console/Commands/Upgrade/UnmigratedRecordFields.php new file mode 100644 index 00000000..f2749a51 --- /dev/null +++ b/src/Console/Commands/Upgrade/UnmigratedRecordFields.php @@ -0,0 +1,194 @@ + field codes, across every tenant + */ + public function codes(): array + { + $codes = []; + + foreach ($this->recordFields() as $field) { + if ($this->hasMissingTargets($field)) { + $codes[] = $field->code; + } + } + + return $codes; + } + + /** + * Record fields whose target is still only on the retiring column. Values do not come + * into it: a field that holds none has a target the drop would erase just the same, and + * the migration step gives it a definition. A field with no target loses nothing, so it + * never blocks the drop. + * + * @return array field codes, across every tenant + */ + public function withoutDefinition(): array + { + $codes = []; + + foreach ($this->recordFields() as $field) { + if ($this->definitionFor($field) instanceof CustomFieldRelationship) { + continue; + } + + if (blank($this->legacyTargetEntityType($field))) { + continue; + } + + $codes[] = $field->code; + } + + return $codes; + } + + /** + * The column is read raw: the model stopped declaring it, and on a host that has already + * migrated it is gone. + */ + public function legacyTargetEntityType(CustomField $field): string + { + $target = $field->getRawOriginal('lookup_type'); + + return is_string($target) ? $target : ''; + } + + /** + * A closed edge counts as migrated: the id reached the ledger and was unlinked there, + * which is not the same as never having arrived, and re-inserting it would resurrect a + * link the user removed. + */ + public function hasMissingTargets(CustomField $field): bool + { + $definition = $this->definitionFor($field); + $missing = false; + + $this->values($field)->chunkById(self::CHUNK_SIZE, function (EloquentCollection $values) use ($definition, &$missing): bool { + $ledger = $definition instanceof CustomFieldRelationship + ? $this->ledgerTargets($definition, $values) + : []; + + foreach ($values as $value) { + foreach ($this->targets($value) as $targetId) { + if (in_array($targetId, $ledger[(string) $value->entity_id] ?? [], true)) { + continue; + } + + $missing = true; + + return false; + } + } + + return true; + }); + + return $missing; + } + + /** + * Every id these records already hold on the definition, open or closed. + * + * @param EloquentCollection $values + * @return array> + */ + public function ledgerTargets(CustomFieldRelationship $definition, EloquentCollection $values): array + { + $links = CustomFields::newLinkModel() + ->newQuery() + ->withoutGlobalScopes() + ->where('relationship_id', $definition->getKey()) + ->whereIn('from_entity_id', $values->pluck('entity_id')->all()) + ->get(); + + $ledger = []; + + foreach ($links as $link) { + $ledger[(string) $link->from_entity_id][] = (string) $link->to_entity_id; + } + + return $ledger; + } + + /** + * @return array + */ + public function targets(CustomFieldValue $value): array + { + $ids = $value->json_value?->all() ?? []; + + return array_values(array_unique(array_map( + static fn (mixed $id): string => (string) $id, + array_filter($ids, static fn (mixed $id): bool => is_int($id) || (is_string($id) && $id !== '')), + ))); + } + + public function definitionFor(CustomField $field): ?CustomFieldRelationship + { + if (! Schema::hasTable((string) config('custom-fields.database.table_names.custom_field_relationships'))) { + return null; + } + + return CustomFields::newRelationshipModel() + ->newQuery() + ->withoutGlobalScopes() + ->where(fn (Builder $query): Builder => $query + ->where('from_field_id', $field->getKey()) + ->orWhere('to_field_id', $field->getKey())) + ->first(); + } + + /** + * @return EloquentCollection + */ + public function recordFields(): EloquentCollection + { + $fields = (string) config('custom-fields.database.table_names.custom_fields'); + $values = (string) config('custom-fields.database.table_names.custom_field_values'); + + if (! Schema::hasTable($fields) || ! Schema::hasTable($values)) { + return CustomFields::newCustomFieldModel()->newCollection(); + } + + return CustomFields::newCustomFieldModel() + ->newQuery() + ->withoutGlobalScopes() + ->where('type', 'record') + ->orderBy('id') + ->get(); + } + + /** + * @return Builder + */ + public function values(CustomField $field): Builder + { + return CustomFields::newValueModel() + ->newQuery() + ->withoutGlobalScopes() + ->where('custom_field_id', $field->getKey()) + ->whereNotNull('json_value'); + } +} diff --git a/src/Console/Commands/Upgrade/UpgradeStep.php b/src/Console/Commands/Upgrade/UpgradeStep.php index c09c4a35..d3e314f2 100644 --- a/src/Console/Commands/Upgrade/UpgradeStep.php +++ b/src/Console/Commands/Upgrade/UpgradeStep.php @@ -6,9 +6,6 @@ use Illuminate\Console\Command; -/** - * Interface for modular upgrade steps in the 2.x → 3.x migration. - */ interface UpgradeStep { /** diff --git a/src/Console/Commands/UpgradeCommand.php b/src/Console/Commands/UpgradeCommand.php index 707dfe71..ea66bcb8 100644 --- a/src/Console/Commands/UpgradeCommand.php +++ b/src/Console/Commands/UpgradeCommand.php @@ -5,38 +5,34 @@ namespace Relaticle\CustomFields\Console\Commands; use Illuminate\Console\Command; -use Relaticle\CustomFields\Console\Commands\Upgrade\Steps\CleanMultiValueValidationRulesStep; use Relaticle\CustomFields\Console\Commands\Upgrade\Steps\ClearCachesStep; -use Relaticle\CustomFields\Console\Commands\Upgrade\Steps\MigrateEmailFormatStep; -use Relaticle\CustomFields\Console\Commands\Upgrade\Steps\MigrateLookupFieldsStep; -use Relaticle\CustomFields\Console\Commands\Upgrade\Steps\MigratePhoneFormatStep; -use Relaticle\CustomFields\Console\Commands\Upgrade\Steps\MigrateValidationRulesFormatStep; +use Relaticle\CustomFields\Console\Commands\Upgrade\Steps\MigrateRecordLinksStep; +use Relaticle\CustomFields\Console\Commands\Upgrade\Steps\PurgeMigratedRecordValuesStep; use Relaticle\CustomFields\Console\Commands\Upgrade\Steps\ValidateSchemaStep; use Relaticle\CustomFields\Console\Commands\Upgrade\UpgradeStep; use Relaticle\CustomFields\Console\Commands\Upgrade\UpgradeStepResult; -/** - * Main upgrade command for custom-fields 2.x → 3.x migration. - */ final class UpgradeCommand extends Command { /** @var string */ protected $signature = 'custom-fields:upgrade {--dry-run : Show what would be migrated without making changes} {--force : Run without confirmation prompts} - {--skip= : Skip specific steps (comma-separated: lookup-fields,email-format,phone-format,validate-schema,clear-caches)}'; + {--purge : Also delete the record values the links step has migrated} + {--skip= : Skip specific steps (comma-separated: validate-schema,migrate-record-links,purge-record-values,clear-caches)}'; /** @var string */ - protected $description = 'Upgrade custom-fields data from 2.x to 3.x'; + protected $description = 'Run the registered custom-fields upgrade steps'; + + public const string STEP_MIGRATE_RECORD_LINKS = 'migrate-record-links'; + + public const string STEP_PURGE_RECORD_VALUES = 'purge-record-values'; /** @var array> */ private const STEPS = [ - 'lookup-fields' => MigrateLookupFieldsStep::class, - 'email-format' => MigrateEmailFormatStep::class, - 'phone-format' => MigratePhoneFormatStep::class, - 'migrate-validation-format' => MigrateValidationRulesFormatStep::class, - 'clean-multivalue-rules' => CleanMultiValueValidationRulesStep::class, 'validate-schema' => ValidateSchemaStep::class, + self::STEP_MIGRATE_RECORD_LINKS => MigrateRecordLinksStep::class, + self::STEP_PURGE_RECORD_VALUES => PurgeMigratedRecordValuesStep::class, 'clear-caches' => ClearCachesStep::class, ]; @@ -48,6 +44,15 @@ public function handle(): int $isForced = (bool) $this->option('force'); $stepsToSkip = $this->getSkippedSteps(); + $unknownSteps = array_diff($stepsToSkip, array_keys(self::STEPS)); + + if ($unknownSteps !== []) { + $this->line(sprintf('Unknown --skip value(s): %s.', implode(', ', $unknownSteps))); + $this->line(sprintf('Valid steps: %s.', implode(', ', array_keys(self::STEPS)))); + + return self::FAILURE; + } + if ($isDryRun) { $this->warn('Running in DRY RUN mode - no changes will be made'); $this->newLine(); @@ -59,7 +64,7 @@ public function handle(): int return self::SUCCESS; } - $results = $this->runSteps($isDryRun, $stepsToSkip); + $results = $this->runSteps($isDryRun); $this->displaySummary($results, $isDryRun); return $this->hasErrors($results) ? self::FAILURE : self::SUCCESS; @@ -68,11 +73,24 @@ public function handle(): int private function displayHeader(): void { $this->newLine(); - $this->line('Custom Fields Upgrade: 2.x → 3.x'); + $this->line('Custom Fields Upgrade'); $this->line(str_repeat('=', 40)); $this->newLine(); } + /** + * Whether a step runs in this invocation. The purge is opt-in: it deletes the store the + * migration was copied from, so nothing but --purge may start it. + */ + public function willRun(string $step): bool + { + if ($step === self::STEP_PURGE_RECORD_VALUES && ! $this->option('purge')) { + return false; + } + + return ! in_array($step, $this->getSkippedSteps(), true); + } + /** * @return list */ @@ -84,21 +102,25 @@ private function getSkippedSteps(): array return []; } - return array_map('trim', explode(',', $skipOption)); + $values = array_map('trim', explode(',', $skipOption)); + + // Only empty elements are dropped: a bare array_filter() also swallows "0", which + // would then reach no step and no unknown-value error either. + return array_values(array_unique(array_filter($values, fn (string $value): bool => $value !== ''))); } /** - * @param list $stepsToSkip * @return array */ - private function runSteps(bool $isDryRun, array $stepsToSkip): array + private function runSteps(bool $isDryRun): array { $results = []; $stepNumber = 1; - $totalSteps = count(self::STEPS) - count($stepsToSkip); + $steps = array_filter(self::STEPS, fn (string $stepClass, string $key): bool => $this->willRun($key), ARRAY_FILTER_USE_BOTH); + $totalSteps = count($steps); foreach (self::STEPS as $key => $stepClass) { - if (in_array($key, $stepsToSkip, true)) { + if (! array_key_exists($key, $steps)) { $this->line(sprintf('Skipping: %s', $key)); $this->newLine(); @@ -116,6 +138,16 @@ private function runSteps(bool $isDryRun, array $stepsToSkip): array $this->displayStepResult($result); $this->newLine(); + // Every later step reads what an earlier one wrote, and the purge deletes the + // store the migration copies from, so a failure ends the run rather than + // handing the next step a state it was told not to trust. + if (! $result->success) { + $this->line(sprintf('Stopping: %s failed.', $key)); + $this->newLine(); + + break; + } + $stepNumber++; } @@ -173,7 +205,6 @@ private function displaySummary(array $results, bool $isDryRun): void $this->line(str_repeat('═', 50)); - // Summary stats $totalProcessed = 0; $totalFailed = 0; foreach ($results as $result) { @@ -187,10 +218,7 @@ private function displaySummary(array $results, bool $isDryRun): void $this->line(sprintf(' Total items failed: %d', $totalFailed)); } - // Manual action reminder $this->newLine(); - $this->warn('Manual Action Required:'); - $this->line(' Update config/custom-fields.php to use the new format if needed.'); $this->line(' See: https://relaticle.github.io/custom-fields/getting-started/upgrade-guide'); } diff --git a/src/Contracts/CustomsFieldsMigrators.php b/src/Contracts/CustomsFieldsMigrators.php deleted file mode 100644 index 2ac1e896..00000000 --- a/src/Contracts/CustomsFieldsMigrators.php +++ /dev/null @@ -1,46 +0,0 @@ - $options - */ - public function options(array $options): CustomsFieldsMigrators; - - /** - * @param class-string $model - */ - public function lookupType(string $model): CustomsFieldsMigrators; - - public function create(): CustomField; - - /** - * @param array $data - */ - public function update(array $data): void; - - public function delete(): void; - - public function activate(): void; - - public function deactivate(): void; -} diff --git a/src/Contracts/EntityConfigurationInterface.php b/src/Contracts/EntityConfigurationInterface.php deleted file mode 100644 index 60d60b49..00000000 --- a/src/Contracts/EntityConfigurationInterface.php +++ /dev/null @@ -1,52 +0,0 @@ - $dependentFieldCodes * @param Collection|null $allFields */ - public function make(CustomField $customField, array $dependentFieldCodes = [], ?Collection $allFields = null): Field; + public function make(CustomField $customField, array $dependentFieldCodes = [], ?Collection $allFields = null, ?Model $record = null): Field; } diff --git a/src/Contracts/InfolistComponentInterface.php b/src/Contracts/InfolistComponentInterface.php index 4c740ee7..c5b58628 100644 --- a/src/Contracts/InfolistComponentInterface.php +++ b/src/Contracts/InfolistComponentInterface.php @@ -5,9 +5,10 @@ namespace Relaticle\CustomFields\Contracts; use Filament\Infolists\Components\Entry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Models\CustomField; interface InfolistComponentInterface { - public function make(CustomField $customField): Entry; + public function make(CustomField $customField, ?Model $record = null): Entry; } diff --git a/src/Contracts/LinkActorResolverInterface.php b/src/Contracts/LinkActorResolverInterface.php new file mode 100644 index 00000000..b8c0add7 --- /dev/null +++ b/src/Contracts/LinkActorResolverInterface.php @@ -0,0 +1,16 @@ + + */ + public static function relationshipModel(): string + { + return self::$relationshipModel; + } + + /** + * Get a new instance of the relationship definition model. + */ + public static function newRelationshipModel(): CustomFieldRelationship + { + $model = self::relationshipModel(); + + return new $model; + } + + /** + * Specify the relationship definition model that should be used by Custom Fields. + */ + public static function useRelationshipModel(string $model): static + { + self::$relationshipModel = $model; + + return new self; + } + + /** + * Get the name of the relationship link model used by the application. + * + * @return class-string + */ + public static function linkModel(): string + { + return self::$linkModel; + } + + /** + * Get a new instance of the relationship link model. + */ + public static function newLinkModel(): CustomFieldLink + { + $model = self::linkModel(); + + return new $model; + } + + /** + * Specify the relationship link model that should be used by Custom Fields. + */ + public static function useLinkModel(string $model): static + { + self::$linkModel = $model; + + return new self; + } + /** * Set the display format for date custom fields. * diff --git a/src/CustomFieldsPlugin.php b/src/CustomFieldsPlugin.php index 13700e16..e5e47723 100644 --- a/src/CustomFieldsPlugin.php +++ b/src/CustomFieldsPlugin.php @@ -11,6 +11,7 @@ use Filament\Support\Concerns\EvaluatesClosures; use Filament\Support\Enums\Width; use InvalidArgumentException; +use Relaticle\CustomFields\Contracts\FieldTypeDefinitionInterface; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\Facades\CustomFieldsType; use Relaticle\CustomFields\FeatureSystem\FeatureManager; @@ -83,6 +84,9 @@ public static function get(): static return $plugin; } + /** + * @param array> | Closure $fieldTypes + */ public function registerFieldTypes(array|Closure $fieldTypes): static { CustomFieldsType::register($fieldTypes); diff --git a/src/CustomFieldsServiceProvider.php b/src/CustomFieldsServiceProvider.php index df9c83d1..90d2b2c1 100644 --- a/src/CustomFieldsServiceProvider.php +++ b/src/CustomFieldsServiceProvider.php @@ -17,8 +17,8 @@ use Relaticle\CustomFields\Console\Commands\MakeCustomFieldsMigrationCommand; use Relaticle\CustomFields\Console\Commands\MakeFieldTypeCommand; use Relaticle\CustomFields\Console\Commands\UpgradeCommand; -use Relaticle\CustomFields\Contracts\CustomsFieldsMigrators; -use Relaticle\CustomFields\Contracts\ValueResolvers; +use Relaticle\CustomFields\Contracts\LinkActorResolverInterface; +use Relaticle\CustomFields\Contracts\ValueResolverInterface; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Filament\Integration\Migrations\CustomFieldsMigrator; @@ -33,10 +33,13 @@ use Relaticle\CustomFields\Providers\ImportsServiceProvider; use Relaticle\CustomFields\Providers\ValidationServiceProvider; use Relaticle\CustomFields\Services\ModelAttributeDiscoveryService; +use Relaticle\CustomFields\Services\Relationships\AuthenticatedActorResolver; +use Relaticle\CustomFields\Services\Relationships\MissingRelationshipDefinitions; use Relaticle\CustomFields\Services\TenantContextService; use Relaticle\CustomFields\Services\ValueResolver\LookupCache; use Relaticle\CustomFields\Services\ValueResolver\ValueResolver; use Relaticle\CustomFields\Services\Visibility\BackendVisibilityService; +use Relaticle\CustomFields\Support\ViewFlavor; use Spatie\LaravelPackageTools\Commands\InstallCommand; use Spatie\LaravelPackageTools\Package; use Spatie\LaravelPackageTools\PackageServiceProvider; @@ -54,9 +57,11 @@ public function bootingPackage(): void $this->app->register(ValidationServiceProvider::class); $this->app->register(EntityServiceProvider::class); - $this->app->singleton(CustomsFieldsMigrators::class, CustomFieldsMigrator::class); - $this->app->singleton(ValueResolvers::class, ValueResolver::class); + $this->app->singleton(CustomFieldsMigrator::class); + $this->app->singleton(ValueResolverInterface::class, ValueResolver::class); + $this->app->singleton(LinkActorResolverInterface::class, AuthenticatedActorResolver::class); $this->app->scoped(LookupCache::class); + $this->app->scoped(MissingRelationshipDefinitions::class); $this->app->singleton(TenantContextService::class); $this->app->singleton(BackendVisibilityService::class); @@ -131,6 +136,8 @@ public function configurePackage(Package $package): void public function packageBooted(): void { + ViewFlavor::validate(); + // Asset Registration FilamentAsset::register( $this->getAssets(), @@ -207,6 +214,9 @@ private function getMigrations(): array return [ 'create_custom_fields_table', 'relax_custom_fields_unique_key', + 'create_relationship_definitions_table', + 'create_relationship_links_table', + 'drop_custom_fields_lookup_type', ]; } } diff --git a/src/Data/AvatarConfiguration.php b/src/Data/AvatarConfiguration.php index 8930b040..44fc157e 100644 --- a/src/Data/AvatarConfiguration.php +++ b/src/Data/AvatarConfiguration.php @@ -26,6 +26,8 @@ public function getCssClass(): string /** * Recreate object from var_export() for Laravel config:cache + * + * @param array $properties */ public static function __set_state(array $properties): self { diff --git a/src/Data/CustomFieldData.php b/src/Data/CustomFieldData.php index c3507f33..e57d65f0 100644 --- a/src/Data/CustomFieldData.php +++ b/src/Data/CustomFieldData.php @@ -29,7 +29,6 @@ public function __construct( public CustomFieldWidth $width = CustomFieldWidth::_100, public ?string $entityType = null, public ?array $options = null, - public ?string $lookupType = null, public ?CustomFieldSettingsData $settings = null, ) {} } diff --git a/src/Data/CustomFieldOptionSettingsData.php b/src/Data/CustomFieldOptionSettingsData.php index 7ed04617..fcb48bfe 100644 --- a/src/Data/CustomFieldOptionSettingsData.php +++ b/src/Data/CustomFieldOptionSettingsData.php @@ -4,14 +4,16 @@ namespace Relaticle\CustomFields\Data; +use Relaticle\CustomFields\Enums\OptionCategory; use Spatie\LaravelData\Attributes\MapName; use Spatie\LaravelData\Data; use Spatie\LaravelData\Mappers\SnakeCaseMapper; #[MapName(SnakeCaseMapper::class)] -class CustomFieldOptionSettingsData extends Data +final class CustomFieldOptionSettingsData extends Data { public function __construct( public ?string $color = null, + public ?OptionCategory $category = null, ) {} } diff --git a/src/Data/CustomFieldSectionSettingsData.php b/src/Data/CustomFieldSectionSettingsData.php index 8be2e908..3cd67783 100644 --- a/src/Data/CustomFieldSectionSettingsData.php +++ b/src/Data/CustomFieldSectionSettingsData.php @@ -9,7 +9,7 @@ use Spatie\LaravelData\Mappers\SnakeCaseMapper; #[MapName(SnakeCaseMapper::class)] -class CustomFieldSectionSettingsData extends Data +final class CustomFieldSectionSettingsData extends Data { /** * @param array $extra Free-form bag for consumer-defined section settings. diff --git a/src/Data/CustomFieldSettingsData.php b/src/Data/CustomFieldSettingsData.php index fbe5d645..027f60a3 100644 --- a/src/Data/CustomFieldSettingsData.php +++ b/src/Data/CustomFieldSettingsData.php @@ -12,8 +12,11 @@ use Spatie\LaravelData\Mappers\SnakeCaseMapper; #[MapName(SnakeCaseMapper::class)] -class CustomFieldSettingsData extends Data +final class CustomFieldSettingsData extends Data { + /** + * @param array $additional + */ public function __construct( public bool $visible_in_list = true, public ?bool $list_toggleable_hidden = null, diff --git a/src/Data/EntityConfigurationData.php b/src/Data/EntityConfigurationData.php index c1d12f6e..3131c5a3 100644 --- a/src/Data/EntityConfigurationData.php +++ b/src/Data/EntityConfigurationData.php @@ -23,6 +23,12 @@ final class EntityConfigurationData extends Data { + /** + * @param array $searchAttributes + * @param ?Collection $features + * @param array $metadata + * @param array $conditionRelations + */ public function __construct( public string $modelClass, public string $alias, @@ -186,6 +192,9 @@ public function getPrimaryAttribute(): string return $this->primaryAttribute; } + /** + * @return array + */ public function getSearchAttributes(): array { return $this->searchAttributes; @@ -201,11 +210,17 @@ public function getRecordPage(): ?string return $this->recordPage; } + /** + * @return array + */ public function getScopes(): array { return []; } + /** + * @return array + */ public function getRelationships(): array { return $this->conditionRelations; @@ -220,6 +235,9 @@ public function getConditionRelations(): array return $this->conditionRelations; } + /** + * @return array + */ public function getFeatures(): array { return $this->features?->map(fn (EntityFeature $f) => $f->value)->toArray() ?? []; @@ -230,6 +248,9 @@ public function getPriority(): int return $this->priority; } + /** + * @return array + */ public function getMetadata(): array { return $this->metadata; @@ -252,6 +273,8 @@ public function createModelInstance(): Model /** * Get a query builder for this entity + * + * @return Builder */ public function newQuery(): Builder { @@ -276,6 +299,7 @@ public static function fromResource(string $resourceClass): self $model = new $modelClass; + /** @var array $features */ $features = [EntityFeature::LOOKUP_SOURCE]; if (in_array(HasCustomFields::class, class_implements($modelClass), true)) { $features[] = EntityFeature::CUSTOM_FIELDS; @@ -309,6 +333,8 @@ public static function fromResource(string $resourceClass): self /** * Recreate object from var_export() for Laravel config:cache * Uses direct constructor instead of ::from() to avoid Laravel Data config dependency + * + * @param array $properties */ public static function __set_state(array $properties): self { diff --git a/src/Data/FieldSlotData.php b/src/Data/FieldSlotData.php new file mode 100644 index 00000000..4d8d8ef0 --- /dev/null +++ b/src/Data/FieldSlotData.php @@ -0,0 +1,33 @@ +> */ + /** @var array> */ public array $validationCapabilities = [], public ?string $settingsDataClass = null, public string|Closure|null $settingsSchema = null, diff --git a/src/Data/RecordLinkPayload.php b/src/Data/RecordLinkPayload.php new file mode 100644 index 00000000..015953f2 --- /dev/null +++ b/src/Data/RecordLinkPayload.php @@ -0,0 +1,84 @@ + $ids + * @param array $confirmed the ids the caller agreed to take from a holder + */ + public function __construct( + public array $ids, + public array $confirmed = [], + ) {} + + public static function fromValue(mixed $value): self + { + $value = self::unwrap($value); + + if (is_array($value) && array_key_exists('ids', $value)) { + $ids = self::ids($value['ids']); + + return new self($ids, self::confirmedIds($value, $ids)); + } + + return new self(self::ids($value)); + } + + /** + * Which records the caller confirmed displacing. `replace` answers for the whole payload, + * which is the documented form a host sends; a picker names the one record the user agreed + * to move, so a record added later never inherits that answer. + * + * @param array $value + * @param array $ids + * @return array + */ + public static function confirmedIds(array $value, array $ids): array + { + $ids = array_map(strval(...), $ids); + + if (($value['replace'] ?? false) === true) { + return $ids; + } + + $confirmed = array_map(strval(...), self::ids($value['confirmed'] ?? [])); + + return array_values(array_intersect($confirmed, $ids)); + } + + /** + * An empty payload is a real value that closes every edge, so only null and blank ids + * fall away here. + * + * @return array + */ + private static function ids(mixed $value): array + { + $value = self::unwrap($value); + $ids = is_array($value) ? $value : [$value]; + + return array_values(array_filter( + $ids, + static fn (mixed $id): bool => is_int($id) || (is_string($id) && $id !== ''), + )); + } + + private static function unwrap(mixed $value): mixed + { + return $value instanceof Arrayable ? $value->toArray() : $value; + } +} diff --git a/src/Data/RelationshipDefinitionData.php b/src/Data/RelationshipDefinitionData.php new file mode 100644 index 00000000..b90a63ac --- /dev/null +++ b/src/Data/RelationshipDefinitionData.php @@ -0,0 +1,31 @@ + $additional + */ public static function fromAdditional(array $additional): self { $code = $additional['currency_code'] ?? 'USD'; diff --git a/src/Data/VisibilityConditionData.php b/src/Data/VisibilityConditionData.php index 3a37d477..fc20b68a 100644 --- a/src/Data/VisibilityConditionData.php +++ b/src/Data/VisibilityConditionData.php @@ -11,7 +11,7 @@ use Spatie\LaravelData\Mappers\SnakeCaseMapper; #[MapName(SnakeCaseMapper::class)] -class VisibilityConditionData extends Data +final class VisibilityConditionData extends Data { public function __construct( public string $field_code, diff --git a/src/Data/VisibilityData.php b/src/Data/VisibilityData.php index 12a98b99..835c77c9 100644 --- a/src/Data/VisibilityData.php +++ b/src/Data/VisibilityData.php @@ -19,7 +19,7 @@ use Spatie\LaravelData\Mappers\SnakeCaseMapper; #[MapName(SnakeCaseMapper::class)] -class VisibilityData extends Data +final class VisibilityData extends Data { /** * @param DataCollection|null $conditions diff --git a/src/EntitySystem/EntityCollection.php b/src/EntitySystem/EntityCollection.php index 8739ab67..67ac96ec 100644 --- a/src/EntitySystem/EntityCollection.php +++ b/src/EntitySystem/EntityCollection.php @@ -11,6 +11,9 @@ use Relaticle\CustomFields\Data\EntityConfigurationData; use Relaticle\CustomFields\Enums\EntityFeature; +/** + * @extends Collection + */ final class EntityCollection extends Collection { /** @@ -92,6 +95,8 @@ public function withoutFeature(string $feature): static /** * Get entities with any of the specified features + * + * @param array $features */ public function withAnyFeature(array $features): static { @@ -108,6 +113,8 @@ public function withAnyFeature(array $features): static /** * Get entities with all of the specified features + * + * @param array $features */ public function withAllFeatures(array $features): static { @@ -164,6 +171,8 @@ public function sortedByLabel(): static /** * Get as options array for selects (alias => label) + * + * @return array */ public function toOptions(bool $usePlural = true): array { @@ -176,6 +185,8 @@ public function toOptions(bool $usePlural = true): array /** * Get as detailed options array with icons + * + * @return array> */ public function toDetailedOptions(): array { @@ -210,6 +221,8 @@ public function whereMetadata(string $key, mixed $value): static /** * Get model classes + * + * @return array */ public function getModelClasses(): array { @@ -220,6 +233,8 @@ public function getModelClasses(): array /** * Get aliases + * + * @return array */ public function getAliases(): array { diff --git a/src/EntitySystem/EntityConfigurator.php b/src/EntitySystem/EntityConfigurator.php index 6a510575..1c7ba204 100644 --- a/src/EntitySystem/EntityConfigurator.php +++ b/src/EntitySystem/EntityConfigurator.php @@ -6,26 +6,29 @@ use Illuminate\Database\Eloquent\Model; use InvalidArgumentException; -use Relaticle\CustomFields\Contracts\EntityConfigurationInterface; /** * Fluent builder for configuring the entire entity management system * Provides clean, discoverable API for global entity configuration */ -final class EntityConfigurator implements EntityConfigurationInterface +final class EntityConfigurator { private bool $autoDiscover = true; + /** @var array */ private array $discoveryPaths; + /** @var array */ private array $discoveryNamespaces = ['App\\Models']; + /** @var array */ private array $excludedModels = []; private bool $cacheEnabled = true; private int $cacheTtl = 3600; + /** @var array> */ private array $entityModels = []; private function __construct() @@ -54,6 +57,8 @@ public function autoDiscover(bool $enabled = true): self /** * Set paths to discover entities from + * + * @param string|array $paths */ public function discover(string|array $paths): self { @@ -64,6 +69,8 @@ public function discover(string|array $paths): self /** * Set namespaces to discover entities from + * + * @param array $namespaces */ public function namespaces(array $namespaces): self { @@ -74,6 +81,8 @@ public function namespaces(array $namespaces): self /** * Only include specific models (disables auto-discovery of others) + * + * @param array> $models */ public function include(array $models): self { @@ -96,6 +105,8 @@ public function include(array $models): self /** * Exclude specific models from discovery and configuration + * + * @param array $models */ public function exclude(array $models): self { @@ -117,6 +128,8 @@ public function cache(bool $enabled = true, int $ttl = 3600): self /** * Configure specific entity models with custom settings + * + * @param array> $entityModels */ public function models(array $entityModels): self { @@ -142,6 +155,8 @@ public function models(array $entityModels): self * * Resolves aliases lazily - if alias is null, we call getMorphClass() at runtime * when the morph map has been registered via Relation::enforceMorphMap(). + * + * @return array> */ private function buildEntitiesArray(): array { @@ -173,6 +188,8 @@ public function getAutoDiscover(): bool /** * Get discovery paths + * + * @return array */ public function getDiscoveryPaths(): array { @@ -181,6 +198,8 @@ public function getDiscoveryPaths(): array /** * Get discovery namespaces + * + * @return array */ public function getDiscoveryNamespaces(): array { @@ -189,6 +208,8 @@ public function getDiscoveryNamespaces(): array /** * Get excluded models + * + * @return array */ public function getExcludedModels(): array { @@ -213,6 +234,8 @@ public function getCacheTtl(): int /** * Get entities array + * + * @return array> */ public function getEntities(): array { @@ -221,6 +244,8 @@ public function getEntities(): array /** * Restore the configurator from var_export + * + * @param array $properties */ public static function __set_state(array $properties): self { diff --git a/src/EntitySystem/EntityDiscovery.php b/src/EntitySystem/EntityDiscovery.php index 8f799987..8fc19405 100644 --- a/src/EntitySystem/EntityDiscovery.php +++ b/src/EntitySystem/EntityDiscovery.php @@ -21,8 +21,13 @@ final class EntityDiscovery { + /** @var array */ private array $discoveredCache = []; + /** + * @param array $paths + * @param array $namespaces + */ public function __construct( private array $paths = [], private array $namespaces = [] @@ -40,6 +45,8 @@ public function __construct( /** * Discover entities from multiple sources + * + * @return array */ public function discover(): array { @@ -80,6 +87,8 @@ public function discover(): array /** * Discover entities from Filament Resources + * + * @return array */ private function discoverFromFilamentResources(): array { @@ -115,6 +124,8 @@ private function discoverFromFilamentResources(): array /** * Discover entities from configured paths + * + * @return array */ private function discoverFromPaths(): array { @@ -147,6 +158,8 @@ private function discoverFromPaths(): array /** * Discover entities from namespaces + * + * @return array */ private function discoverFromNamespaces(): array { @@ -285,6 +298,8 @@ private function getModelPrimaryAttribute(string $modelClass): string /** * Get model search attributes + * + * @return array */ private function getModelSearchAttributes(string $modelClass): array { @@ -326,6 +341,8 @@ private function getClassNameFromFile(string $path): ?string /** * Get classes in a namespace (using declared classes) + * + * @return array */ private function getClassesInNamespace(string $namespace): array { diff --git a/src/EntitySystem/EntityManager.php b/src/EntitySystem/EntityManager.php index 8f874a14..9b06ec6b 100644 --- a/src/EntitySystem/EntityManager.php +++ b/src/EntitySystem/EntityManager.php @@ -11,11 +11,10 @@ use Filament\Resources\Resource; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Traits\Macroable; -use Relaticle\CustomFields\Contracts\EntityManagerInterface; use Relaticle\CustomFields\Data\EntityConfigurationData; use Relaticle\CustomFields\Enums\EntityFeature; -final class EntityManager implements EntityManagerInterface +final class EntityManager { use Macroable; @@ -23,14 +22,17 @@ final class EntityManager implements EntityManagerInterface private const int CACHE_TTL = 3600; // 1 hour + /** @var array|Closure> */ private array $entities = []; + /** @var ?array */ private ?array $cachedEntities = null; private ?EntityDiscovery $discovery = null; private bool $discoveryEnabled = false; + /** @var array */ private array $resolvingCallbacks = []; public function __construct( @@ -39,6 +41,8 @@ public function __construct( /** * Register entities + * + * @param array|Closure $entities */ public function register(array|Closure $entities): static { @@ -80,6 +84,8 @@ public function hasEntity(string $classOrAlias): bool /** * Enable automatic discovery of entities + * + * @param array $paths */ public function enableDiscovery(array $paths = []): static { @@ -144,6 +150,8 @@ public function resolving(Closure $callback): static /** * Build the entity cache + * + * @return array */ private function buildEntityCache(): array { @@ -178,6 +186,9 @@ private function buildEntityCache(): array /** * Resolve entities from various input types + * + * @param array|Closure $entities + * @return array */ private function resolveEntities(array|Closure $entities): array { diff --git a/src/EntitySystem/EntityModel.php b/src/EntitySystem/EntityModel.php index cf08b353..a3dd6259 100644 --- a/src/EntitySystem/EntityModel.php +++ b/src/EntitySystem/EntityModel.php @@ -19,6 +19,8 @@ final class EntityModel { /** * Create entity configuration array from model class + * + * @return array */ public static function for(string $modelClass): array { @@ -30,8 +32,12 @@ public static function for(string $modelClass): array /** * Configure entity with custom settings * + * @param array $searchAttributes + * @param array $features + * @param array $metadata * @param array $conditionRelations path => label allowlist of relation paths usable as * cross-record visibility-condition sources for this entity. + * @return array */ public static function configure( string $modelClass, @@ -101,6 +107,8 @@ private static function validateModelClass(string $modelClass): void /** * Set smart defaults based on the model class * Icon is resolved lazily in EntityConfigurationData::getIcon() at runtime + * + * @return array */ private static function setSmartDefaults(string $modelClass): array { @@ -148,6 +156,8 @@ private static function guessPrimaryAttribute(Model $model): string /** * Guess the best search attributes for this model + * + * @return array */ private static function guessSearchAttributes(string $primaryAttribute): array { diff --git a/src/Enums/CustomFieldsFeature.php b/src/Enums/CustomFieldsFeature.php index eaf8ee3e..e2458d92 100644 --- a/src/Enums/CustomFieldsFeature.php +++ b/src/Enums/CustomFieldsFeature.php @@ -37,4 +37,23 @@ enum CustomFieldsFeature: string case SYSTEM_MANAGEMENT_INTERFACE = 'system_management_interface'; case SYSTEM_MULTI_TENANCY = 'system_multi_tenancy'; case SYSTEM_SECTIONS = 'system_sections'; + case SYSTEM_RELATIONSHIPS = 'system_relationships'; + + /** + * The package default a host inherits for a flag its published config does not list. + * A flag is off by default only when turning it on changes stored, validated, or + * displayed data; every other flag is on. + */ + public function isEnabledByDefault(): bool + { + return match ($this) { + self::FIELD_CODE_AUTO_GENERATE, + self::FIELD_MULTI_VALUE, + self::FIELD_UNIQUE_VALUE, + self::MODEL_ATTRIBUTE_CONDITIONS, + self::UI_TOGGLEABLE_COLUMNS_HIDDEN_DEFAULT, + self::SYSTEM_MULTI_TENANCY => false, + default => true, + }; + } } diff --git a/src/Enums/ImportDateFormat.php b/src/Enums/ImportDateFormat.php index d3aa81ce..09b38031 100644 --- a/src/Enums/ImportDateFormat.php +++ b/src/Enums/ImportDateFormat.php @@ -38,9 +38,9 @@ enum ImportDateFormat: string implements HasLabel public function getLabel(): string { return match ($this) { - self::ISO => 'ISO standard', - self::EUROPEAN => 'European (day first)', - self::AMERICAN => 'American (month first)', + self::ISO => __('custom-fields::custom-fields.enums.import_date_format.iso'), + self::EUROPEAN => __('custom-fields::custom-fields.enums.import_date_format.european'), + self::AMERICAN => __('custom-fields::custom-fields.enums.import_date_format.american'), }; } diff --git a/src/Enums/ImportNumberFormat.php b/src/Enums/ImportNumberFormat.php index 7a30e759..e4f8925c 100644 --- a/src/Enums/ImportNumberFormat.php +++ b/src/Enums/ImportNumberFormat.php @@ -20,8 +20,8 @@ enum ImportNumberFormat: string implements HasLabel public function getLabel(): string { return match ($this) { - self::POINT => 'Point', - self::COMMA => 'Comma', + self::POINT => __('custom-fields::custom-fields.enums.import_number_format.point'), + self::COMMA => __('custom-fields::custom-fields.enums.import_number_format.comma'), }; } diff --git a/src/Enums/OptionCategory.php b/src/Enums/OptionCategory.php new file mode 100644 index 00000000..b721a066 --- /dev/null +++ b/src/Enums/OptionCategory.php @@ -0,0 +1,30 @@ + __('custom-fields::custom-fields.enums.option_category.unstarted'), + self::Started => __('custom-fields::custom-fields.enums.option_category.started'), + self::Completed => __('custom-fields::custom-fields.enums.option_category.completed'), + self::Cancelled => __('custom-fields::custom-fields.enums.option_category.cancelled'), + }; + } + + public function isTerminal(): bool + { + return in_array($this, [self::Completed, self::Cancelled], true); + } +} diff --git a/src/Enums/RelationshipCardinality.php b/src/Enums/RelationshipCardinality.php new file mode 100644 index 00000000..e74fbcf4 --- /dev/null +++ b/src/Enums/RelationshipCardinality.php @@ -0,0 +1,79 @@ + self::ManyToOne, + self::ManyToOne => self::OneToMany, + self::OneToOne, self::ManyToMany => $this, + }; + } + + /** + * The same relationship with the from record holding one target or many, leaving the to + * side where it is. A face that asks only how many records its own field holds answers + * for one end, so the other end's constraint has to survive the answer. + */ + public function fromSideHolds(bool $many): self + { + return match (true) { + $many && $this->toSideIsSingle() => self::OneToMany, + $many => self::ManyToMany, + $this->toSideIsSingle() => self::OneToOne, + default => self::ManyToOne, + }; + } + + /** + * Whether moving to the given cardinality takes an end from many records to one, which + * closes the edges that no longer fit. + */ + public function narrows(self $to): bool + { + if (! $this->fromSideIsSingle() && $to->fromSideIsSingle()) { + return true; + } + + return ! $this->toSideIsSingle() && $to->toSideIsSingle(); + } + + public function getLabel(): string + { + return match ($this) { + self::OneToOne => __('custom-fields::custom-fields.enums.relationship_cardinality.one_to_one'), + self::OneToMany => __('custom-fields::custom-fields.enums.relationship_cardinality.one_to_many'), + self::ManyToOne => __('custom-fields::custom-fields.enums.relationship_cardinality.many_to_one'), + self::ManyToMany => __('custom-fields::custom-fields.enums.relationship_cardinality.many_to_many'), + }; + } +} diff --git a/src/Enums/UiFlavor.php b/src/Enums/UiFlavor.php new file mode 100644 index 00000000..6cfe4fbd --- /dev/null +++ b/src/Enums/UiFlavor.php @@ -0,0 +1,12 @@ + | string> | Closure $fieldTypes + * @param array> | Closure $fieldTypes */ public static function register(array|Closure $fieldTypes): void { - static::resolved(function (FieldManager $fieldTypeManager) use ($fieldTypes): void { + self::resolved(function (FieldManager $fieldTypeManager) use ($fieldTypes): void { $fieldTypeManager->register($fieldTypes); }); } diff --git a/src/Facades/Entities.php b/src/Facades/Entities.php index 9bbb1ccb..2b69ebec 100644 --- a/src/Facades/Entities.php +++ b/src/Facades/Entities.php @@ -17,8 +17,8 @@ * @method static EntityCollection getEntities() * @method static EntityConfigurationData|null getEntity(string $classOrAlias) * @method static bool hasEntity(string $classOrAlias) - * @method static EntityManager register(array|Closure $entities) - * @method static EntityManager enableDiscovery(array $paths = []) + * @method static EntityManager register(array|Closure $entities) + * @method static EntityManager enableDiscovery(array $paths = []) * @method static EntityManager disableDiscovery() * @method static EntityManager clearCache() * @method static EntityCollection getEntitiesWithFeature(string $feature) @@ -27,7 +27,7 @@ * * @see EntityManager */ -class Entities extends Facade +final class Entities extends Facade { protected static function getFacadeAccessor(): string { @@ -36,20 +36,24 @@ protected static function getFacadeAccessor(): string /** * Register entities with deferred execution + * + * @param array|Closure $entities */ public static function register(array|Closure $entities): void { - static::resolved(function (EntityManager $manager) use ($entities): void { + self::resolved(function (EntityManager $manager) use ($entities): void { $manager->register($entities); }); } /** * Enable discovery with deferred execution + * + * @param array $paths */ public static function discover(array $paths = []): void { - static::resolved(function (EntityManager $manager) use ($paths): void { + self::resolved(function (EntityManager $manager) use ($paths): void { $manager->enableDiscovery($paths); }); } @@ -59,15 +63,17 @@ public static function discover(array $paths = []): void */ public static function registerEntity(EntityConfigurationData $entity): void { - static::register([$entity]); + self::register([$entity]); } /** * Register an entity from array configuration + * + * @param array $config */ public static function registerFromArray(array $config): void { - static::register([$config]); + self::register([$config]); } /** @@ -75,7 +81,7 @@ public static function registerFromArray(array $config): void */ public static function registerFromResource(string $resourceClass): void { - static::register([$resourceClass]); + self::register([$resourceClass]); } /** @@ -83,7 +89,7 @@ public static function registerFromResource(string $resourceClass): void */ public static function withCustomFields(): EntityCollection { - return static::getEntities()->withCustomFields(); + return self::getEntities()->withCustomFields(); } /** @@ -91,7 +97,7 @@ public static function withCustomFields(): EntityCollection */ public static function globallyManaged(): EntityCollection { - return static::getEntities()->globallyManaged(); + return self::getEntities()->globallyManaged(); } /** @@ -99,18 +105,20 @@ public static function globallyManaged(): EntityCollection */ public static function asLookupSources(): EntityCollection { - return static::getEntities()->asLookupSources(); + return self::getEntities()->asLookupSources(); } /** * Get entities as options array + * + * @return array */ public static function getOptions(bool $onlyCustomFields = true, bool $usePlural = true, bool $onlyGloballyManaged = false): array { $entities = match (true) { - $onlyGloballyManaged => static::globallyManaged(), - $onlyCustomFields => static::withCustomFields(), - default => static::getEntities(), + $onlyGloballyManaged => self::globallyManaged(), + $onlyCustomFields => self::withCustomFields(), + default => self::getEntities(), }; return $entities->sortedByLabel()->toOptions($usePlural); @@ -118,10 +126,12 @@ public static function getOptions(bool $onlyCustomFields = true, bool $usePlural /** * Get lookup options + * + * @return array */ public static function getLookupOptions(bool $usePlural = true): array { - return static::asLookupSources() + return self::asLookupSources() ->sortedByLabel() ->toOptions($usePlural); } diff --git a/src/FeatureSystem/FeatureConfigurator.php b/src/FeatureSystem/FeatureConfigurator.php index 64403243..5788805c 100644 --- a/src/FeatureSystem/FeatureConfigurator.php +++ b/src/FeatureSystem/FeatureConfigurator.php @@ -11,6 +11,7 @@ */ final class FeatureConfigurator { + /** @var array */ private array $features = []; private function __construct() @@ -51,15 +52,20 @@ public function disable(CustomFieldsFeature ...$features): self } /** - * Check if a feature is enabled + * Check if a feature is enabled. + * + * A flag this configurator does not list falls back to the package default, so a config + * published before the flag existed inherits it instead of silently running it off. */ public function isEnabled(CustomFieldsFeature $feature): bool { - return $this->features[$feature->value] ?? false; + return $this->features[$feature->value] ?? $feature->isEnabledByDefault(); } /** * Restore the configurator from var_export + * + * @param array $properties */ public static function __set_state(array $properties): self { diff --git a/src/FieldTypeSystem/Concerns/ConfiguresCapabilities.php b/src/FieldTypeSystem/Concerns/ConfiguresCapabilities.php new file mode 100644 index 00000000..4106370c --- /dev/null +++ b/src/FieldTypeSystem/Concerns/ConfiguresCapabilities.php @@ -0,0 +1,231 @@ +|null */ + private ?array $visibilityOperators = null; + + /** + * Configure searchability in tables + */ + public function searchable(bool $searchable = true): self + { + $this->searchable = $searchable; + + return $this; + } + + /** + * Configure sortability in tables + */ + public function sortable(bool $sortable = true): self + { + $this->sortable = $sortable; + + return $this; + } + + /** + * Configure filterability in tables + */ + public function filterable(bool $filterable = true): self + { + $this->filterable = $filterable; + + return $this; + } + + /** + * Configure encryption capability + */ + public function encryptable(bool $encryptable = true): self + { + $this->encryptable = $encryptable; + + return $this; + } + + /** + * Configure whether field accepts arbitrary values (like tags input) + */ + public function withArbitraryValues(bool $accepts = true): self + { + $this->acceptsArbitraryValues = $accepts; + + return $this; + } + + /** + * Configure whether field supports multiple values (e.g., multiple emails, phones) + */ + public function supportsMultiValue(bool $supports = true): self + { + $this->supportsMultiValue = $supports; + + return $this; + } + + /** + * Configure whether field supports unique value constraint per entity type + */ + public function supportsUniqueConstraint(bool $supports = true): self + { + $this->supportsUniqueConstraint = $supports; + + return $this; + } + + /** + * Enable encryption for this field (text fields) + */ + public function encrypted(): self + { + $this->encryptable(); + + return $this; + } + + /** + * Configure as a long text field (textarea) + */ + public function longText(): self + { + return $this; + } + + /** + * Allow users to create new options on the fly (choice fields) + */ + public function allowArbitraryValues(): self + { + $this->withArbitraryValues(); + + return $this; + } + + /** + * Field doesn't need user-configured options (choice fields) + * This disables database options UI and enables dynamic extraction from components + */ + public function withoutUserOptions(): self + { + $this->withoutUserOptions = true; + + return $this; + } + + /** + * Override the default visibility operators derived from the data type. + * + * @param array $operators + */ + public function visibilityOperators(array $operators): self + { + $this->visibilityOperators = $operators; + + return $this; + } + + /** + * Field points at records of another entity, configured by a relationship definition + * rather than by the user-defined options UI. + */ + public function requiresRelationship(bool $requires = true): self + { + $this->requiresRelationship = $requires; + + return $this; + } + + /** + * Field carries a relationship the user configures on both ends: a second slot, a + * cardinality, symmetry. The one-way types share the substrate and answer false, which is + * what keeps their configuration and their surfaces the plain ones they have always been. + */ + public function supportsPairing(bool $supports = true): self + { + $this->supportsPairing = $supports; + + return $this; + } + + /** + * Each option of the field means a workflow state, so the options editor asks for a + * category beside every name and the migrator accepts one. A plain choice field answers + * false and keeps the free-text list it has always had. + */ + public function carriesOptionCategories(bool $carries = true): self + { + $this->carriesOptionCategories = $carries; + + return $this; + } + + /** + * Check if field is searchable + */ + public function isSearchable(): bool + { + return $this->searchable; + } + + /** + * Check if field is sortable + */ + public function isSortable(): bool + { + return $this->sortable; + } + + /** + * Check if field is filterable + */ + public function isFilterable(): bool + { + return $this->filterable; + } + + /** + * Check if field is encryptable + */ + public function isEncryptable(): bool + { + return $this->encryptable; + } + + /** + * Check if field accepts arbitrary values + */ + public function acceptsArbitraryValues(): bool + { + return $this->acceptsArbitraryValues; + } +} diff --git a/src/FieldTypeSystem/Concerns/ConfiguresComponents.php b/src/FieldTypeSystem/Concerns/ConfiguresComponents.php new file mode 100644 index 00000000..0ca00a95 --- /dev/null +++ b/src/FieldTypeSystem/Concerns/ConfiguresComponents.php @@ -0,0 +1,104 @@ +formComponent = $component; + + return $this; + } + + /** + * Set the table column for this field type + */ + public function tableColumn(string|Closure $column): self + { + $this->tableColumn = $column; + + return $this; + } + + /** + * Set the table filter for this field type + */ + public function tableFilter(string|Closure $filter): self + { + $this->tableFilter = $filter; + + return $this; + } + + /** + * Set the infolist entry for this field type + */ + public function infolistEntry(string|Closure $entry): self + { + $this->infolistEntry = $entry; + + return $this; + } + + /** + * Set the priority for field ordering + */ + public function priority(int $priority): self + { + $this->priority = $priority; + + return $this; + } + + public function withSettings(string $dataClass, string|Closure $schema): self + { + if (! is_subclass_of($dataClass, Data::class)) { + throw new InvalidArgumentException('Settings data class must extend '.Data::class); + } + + $this->settingsDataClass = $dataClass; + $this->settingsSchema = $schema; + + return $this; + } + + /** + * Get the form component + */ + public function getFormComponent(): string|Closure|null + { + return $this->formComponent; + } + + /** + * Get the priority + */ + public function getPriority(): int + { + return $this->priority; + } +} diff --git a/src/FieldTypeSystem/Concerns/ConfiguresIdentity.php b/src/FieldTypeSystem/Concerns/ConfiguresIdentity.php new file mode 100644 index 00000000..7faae70c --- /dev/null +++ b/src/FieldTypeSystem/Concerns/ConfiguresIdentity.php @@ -0,0 +1,68 @@ +key = $key; + + return $this; + } + + /** + * Set the field label + */ + public function label(string $label): self + { + $this->label = $label; + + return $this; + } + + /** + * Set the field icon + */ + public function icon(string $icon): self + { + $this->icon = $icon; + + return $this; + } + + /** + * Get the field key + */ + public function getKey(): string + { + return $this->key; + } + + /** + * Get the field label + */ + public function getLabel(): string + { + return $this->label; + } + + /** + * Get the field icon + */ + public function getIcon(): string + { + return $this->icon; + } +} diff --git a/src/FieldTypeSystem/Concerns/ConfiguresImportExport.php b/src/FieldTypeSystem/Concerns/ConfiguresImportExport.php new file mode 100644 index 00000000..d1632602 --- /dev/null +++ b/src/FieldTypeSystem/Concerns/ConfiguresImportExport.php @@ -0,0 +1,70 @@ +importExample = $example; + + return $this; + } + + /** + * Set custom import column transformer + */ + public function importTransformer(Closure $transformer): self + { + $this->importTransformer = $transformer; + + return $this; + } + + /** + * Set custom export value transformer + */ + public function exportTransformer(Closure $transformer): self + { + $this->exportTransformer = $transformer; + + return $this; + } + + /** + * Get import example + */ + public function getImportExample(): ?string + { + return $this->importExample; + } + + /** + * Get import transformer + */ + public function getImportTransformer(): ?Closure + { + return $this->importTransformer; + } + + /** + * Get export transformer + */ + public function getExportTransformer(): ?Closure + { + return $this->exportTransformer; + } +} diff --git a/src/FieldTypeSystem/Concerns/ConfiguresValidationRules.php b/src/FieldTypeSystem/Concerns/ConfiguresValidationRules.php new file mode 100644 index 00000000..9f4945fe --- /dev/null +++ b/src/FieldTypeSystem/Concerns/ConfiguresValidationRules.php @@ -0,0 +1,91 @@ + */ + private array $defaultValidationRules = []; + + /** @var array */ + private array $defaultItemValidationRules = []; + + /** @var array> */ + private array $validationCapabilities = []; + + /** + * Set default validation rules that are always applied. + * + * @param array $rules + */ + public function defaultValidationRules(array $rules): self + { + $this->defaultValidationRules = $rules; + + return $this; + } + + /** + * Set default validation rules for individual items in multi-value fields. + * Only available for MULTI_CHOICE data type. + * + * @param array $rules + * + * @throws InvalidArgumentException if used with non-MULTI_CHOICE data type + */ + public function defaultItemValidationRules(array $rules): self + { + if ($this->dataType !== FieldDataType::MULTI_CHOICE) { + throw new InvalidArgumentException( + 'defaultItemValidationRules is only available for multi-value field types (MULTI_CHOICE)' + ); + } + + $this->defaultItemValidationRules = $rules; + + return $this; + } + + /** @param class-string ...$capabilityClasses */ + public function withValidationCapabilities(string ...$capabilityClasses): self + { + $this->validationCapabilities = [ + ...$this->validationCapabilities, + ...$capabilityClasses, + ]; + + return $this; + } + + /** @return array> */ + public function getValidationCapabilities(): array + { + return $this->validationCapabilities; + } + + /** + * Get the default validation rules (always applied) + * + * @return array + */ + public function getDefaultValidationRules(): array + { + return $this->defaultValidationRules; + } + + /** + * Get the default validation rules for individual items in multi-value fields. + * + * @return array + */ + public function getDefaultItemValidationRules(): array + { + return $this->defaultItemValidationRules; + } +} diff --git a/src/FieldTypeSystem/Definitions/RecordFieldType.php b/src/FieldTypeSystem/Definitions/RecordFieldType.php index 7c5125f5..8241ece9 100644 --- a/src/FieldTypeSystem/Definitions/RecordFieldType.php +++ b/src/FieldTypeSystem/Definitions/RecordFieldType.php @@ -15,10 +15,15 @@ class RecordFieldType extends BaseFieldType { + /** + * The key every caller that has to single out a record field compares against. + */ + public const string KEY = 'record'; + public function configure(): FieldSchema { return FieldSchema::multiChoice() - ->key('record') + ->key(self::KEY) ->label(__('custom-fields::custom-fields.field_types.record')) ->icon('heroicon-o-link') ->formComponent(RecordSelectComponent::class) @@ -26,10 +31,9 @@ public function configure(): FieldSchema ->tableFilter(RecordFilter::class) ->infolistEntry(RecordEntry::class) ->withoutUserOptions() - ->requiresLookupType() - ->supportsMultiValue() - ->sortable(false) - ->searchable(false) + ->requiresRelationship() + ->sortable() + ->searchable() ->filterable() ->priority(45) ->withValidationCapabilities( diff --git a/src/FieldTypeSystem/Definitions/RelationshipFieldType.php b/src/FieldTypeSystem/Definitions/RelationshipFieldType.php new file mode 100644 index 00000000..6ec8d48e --- /dev/null +++ b/src/FieldTypeSystem/Definitions/RelationshipFieldType.php @@ -0,0 +1,46 @@ +key(self::KEY) + ->label(__('custom-fields::custom-fields.field_types.relationship')) + ->icon('heroicon-o-arrows-right-left') + ->formComponent(RelationshipSelectComponent::class) + ->tableColumn(RecordColumn::class) + ->tableFilter(RecordFilter::class) + ->infolistEntry(RecordEntry::class) + ->withoutUserOptions() + ->requiresRelationship() + ->supportsPairing() + ->sortable() + ->searchable() + ->filterable() + ->priority(46) + ->withValidationCapabilities( + MinSelectionsCapability::class, + MaxSelectionsCapability::class, + ) + ->importExample('01JJXYZ123ABC456DEF789GHI'); + } +} diff --git a/src/FieldTypeSystem/Definitions/RichEditorFieldType.php b/src/FieldTypeSystem/Definitions/RichEditorFieldType.php index 3cae285b..f850384e 100644 --- a/src/FieldTypeSystem/Definitions/RichEditorFieldType.php +++ b/src/FieldTypeSystem/Definitions/RichEditorFieldType.php @@ -4,6 +4,7 @@ namespace Relaticle\CustomFields\FieldTypeSystem\Definitions; +use JsonException; use Relaticle\CustomFields\FieldTypeSystem\BaseFieldType; use Relaticle\CustomFields\FieldTypeSystem\FieldSchema; use Relaticle\CustomFields\Filament\Integration\Components\Forms\RichEditorComponent; @@ -11,6 +12,7 @@ use Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns\RichTextColumn; use Relaticle\CustomFields\Validation\Capabilities\MaxLengthCapability; use Relaticle\CustomFields\Validation\Capabilities\MinLengthCapability; +use RuntimeException; /** * ABOUTME: Field type definition for Rich Editor fields @@ -39,7 +41,11 @@ public function configure(): FieldSchema } if (is_array($state)) { - return json_encode($state); + try { + return json_encode($state, JSON_THROW_ON_ERROR); + } catch (JsonException $jsonException) { + throw new RuntimeException('Unable to encode rich editor content as JSON: '.$jsonException->getMessage(), $jsonException->getCode(), previous: $jsonException); + } } $text = (string) $state; @@ -49,6 +55,11 @@ public function configure(): FieldSchema } $lines = preg_split('/\r\n|\r|\n/', $text); + + if ($lines === false) { + throw new RuntimeException('Unable to split rich editor content into lines: '.preg_last_error_msg()); + } + $paragraphs = array_map(fn (string $line): string => '

'.e($line).'

', $lines); return implode('', $paragraphs); diff --git a/src/FieldTypeSystem/Definitions/StatusFieldType.php b/src/FieldTypeSystem/Definitions/StatusFieldType.php new file mode 100644 index 00000000..5b78ffd1 --- /dev/null +++ b/src/FieldTypeSystem/Definitions/StatusFieldType.php @@ -0,0 +1,37 @@ +key(self::KEY) + ->label(__('custom-fields::custom-fields.field_types.status')) + ->icon('mdi-progress-check') + ->formComponent(SelectComponent::class) + ->tableColumn(SingleChoiceColumn::class) + ->tableFilter(SelectFilter::class) + ->infolistEntry(SingleChoiceEntry::class) + ->carriesOptionCategories() + ->priority(51) + ->filterable(); + } +} diff --git a/src/FieldTypeSystem/FieldManager.php b/src/FieldTypeSystem/FieldManager.php index 6dd2c363..6b6e38a3 100644 --- a/src/FieldTypeSystem/FieldManager.php +++ b/src/FieldTypeSystem/FieldManager.php @@ -24,8 +24,10 @@ use Relaticle\CustomFields\FieldTypeSystem\Definitions\PhoneFieldType; use Relaticle\CustomFields\FieldTypeSystem\Definitions\RadioFieldType; use Relaticle\CustomFields\FieldTypeSystem\Definitions\RecordFieldType; +use Relaticle\CustomFields\FieldTypeSystem\Definitions\RelationshipFieldType; use Relaticle\CustomFields\FieldTypeSystem\Definitions\RichEditorFieldType; use Relaticle\CustomFields\FieldTypeSystem\Definitions\SelectFieldType; +use Relaticle\CustomFields\FieldTypeSystem\Definitions\StatusFieldType; use Relaticle\CustomFields\FieldTypeSystem\Definitions\TagsInputFieldType; use Relaticle\CustomFields\FieldTypeSystem\Definitions\TextareaFieldType; use Relaticle\CustomFields\FieldTypeSystem\Definitions\TextFieldType; @@ -56,13 +58,15 @@ final class FieldManager DateFieldType::class, DateTimeFieldType::class, SelectFieldType::class, + StatusFieldType::class, MultiSelectFieldType::class, RecordFieldType::class, + RelationshipFieldType::class, FileUploadFieldType::class, ]; /** - * @var array | string> | Closure> + * @var array> | Closure> */ private array $fieldTypes = []; @@ -77,7 +81,7 @@ final class FieldManager private array $cachedInstances = []; /** - * @param array | string> | Closure $fieldTypes + * @param array> | Closure $fieldTypes */ public function register(array|Closure $fieldTypes): static { diff --git a/src/FieldTypeSystem/FieldSchema.php b/src/FieldTypeSystem/FieldSchema.php index fd39d526..a290a027 100644 --- a/src/FieldTypeSystem/FieldSchema.php +++ b/src/FieldTypeSystem/FieldSchema.php @@ -4,80 +4,27 @@ namespace Relaticle\CustomFields\FieldTypeSystem; -use Closure; -use InvalidArgumentException; -use Relaticle\CustomFields\Contracts\ValidationCapability; use Relaticle\CustomFields\Data\FieldTypeData; use Relaticle\CustomFields\Enums\FieldDataType; -use Relaticle\CustomFields\Enums\VisibilityOperator; -use Spatie\LaravelData\Data; +use Relaticle\CustomFields\FieldTypeSystem\Concerns\ConfiguresCapabilities; +use Relaticle\CustomFields\FieldTypeSystem\Concerns\ConfiguresComponents; +use Relaticle\CustomFields\FieldTypeSystem\Concerns\ConfiguresIdentity; +use Relaticle\CustomFields\FieldTypeSystem\Concerns\ConfiguresImportExport; +use Relaticle\CustomFields\FieldTypeSystem\Concerns\ConfiguresValidationRules; /** * Schema builder for defining field type capabilities and behaviors. * Provides a chainable API for configuring field type features. */ -class FieldSchema +final class FieldSchema { - private FieldDataType $dataType; - - // Field identity - private string $key = ''; - - private string $label = ''; - - private string $icon = ''; - - // Component definitions - private string|Closure|null $formComponent = null; - - private string|Closure|null $tableColumn = null; - - private string|Closure|null $tableFilter = null; - - private string|Closure|null $infolistEntry = null; - - // Field properties - private int $priority = 500; - - private array $defaultValidationRules = []; - - private array $defaultItemValidationRules = []; - - // Validation capabilities - /** @var array> */ - private array $validationCapabilities = []; - - // Capabilities - private bool $searchable = true; - - private bool $sortable = true; - - private bool $filterable = false; - - private bool $encryptable = false; - - private bool $acceptsArbitraryValues = false; - - private bool $supportsMultiValue = false; + use ConfiguresCapabilities; + use ConfiguresComponents; + use ConfiguresIdentity; + use ConfiguresImportExport; + use ConfiguresValidationRules; - private bool $supportsUniqueConstraint = false; - - protected bool $withoutUserOptions = false; - - private bool $requiresLookupType = false; - - /** @var array|null */ - private ?array $visibilityOperators = null; - - private ?string $settingsDataClass = null; - - private string|Closure|null $settingsSchema = null; - - private ?string $importExample = null; - - private ?Closure $importTransformer = null; - - private ?Closure $exportTransformer = null; + private FieldDataType $dataType; public function __construct(FieldDataType $dataType) { @@ -176,304 +123,6 @@ public static function multiChoice(): self return new self(FieldDataType::MULTI_CHOICE); } - // ========== Field Identity Configuration Methods ========== - - /** - * Set the field key - */ - public function key(string $key): self - { - $this->key = $key; - - return $this; - } - - /** - * Set the field label - */ - public function label(string $label): self - { - $this->label = $label; - - return $this; - } - - /** - * Set the field icon - */ - public function icon(string $icon): self - { - $this->icon = $icon; - - return $this; - } - - // ========== Component Configuration Methods ========== - - /** - * Set the form component for this field type - */ - public function formComponent(string|Closure $component): self - { - $this->formComponent = $component; - - return $this; - } - - /** - * Set the table column for this field type - */ - public function tableColumn(string|Closure $column): self - { - $this->tableColumn = $column; - - return $this; - } - - /** - * Set the table filter for this field type - */ - public function tableFilter(string|Closure $filter): self - { - $this->tableFilter = $filter; - - return $this; - } - - /** - * Set the infolist entry for this field type - */ - public function infolistEntry(string|Closure $entry): self - { - $this->infolistEntry = $entry; - - return $this; - } - - /** - * Set the priority for field ordering - */ - public function priority(int $priority): self - { - $this->priority = $priority; - - return $this; - } - - /** - * Set default validation rules that are always applied. - * - * @param array $rules - */ - public function defaultValidationRules(array $rules): self - { - $this->defaultValidationRules = $rules; - - return $this; - } - - /** - * Set default validation rules for individual items in multi-value fields. - * Only available for MULTI_CHOICE data type. - * - * @param array $rules - * - * @throws InvalidArgumentException if used with non-MULTI_CHOICE data type - */ - public function defaultItemValidationRules(array $rules): self - { - if ($this->dataType !== FieldDataType::MULTI_CHOICE) { - throw new InvalidArgumentException( - 'defaultItemValidationRules is only available for multi-value field types (MULTI_CHOICE)' - ); - } - - $this->defaultItemValidationRules = $rules; - - return $this; - } - - // ========== Common Capability Methods ========== - - /** - * Configure searchability in tables - */ - public function searchable(bool $searchable = true): self - { - $this->searchable = $searchable; - - return $this; - } - - /** - * Configure sortability in tables - */ - public function sortable(bool $sortable = true): self - { - $this->sortable = $sortable; - - return $this; - } - - /** - * Configure filterability in tables - */ - public function filterable(bool $filterable = true): self - { - $this->filterable = $filterable; - - return $this; - } - - /** - * Configure encryption capability - */ - public function encryptable(bool $encryptable = true): self - { - $this->encryptable = $encryptable; - - return $this; - } - - /** - * Configure whether field accepts arbitrary values (like tags input) - */ - public function withArbitraryValues(bool $accepts = true): self - { - $this->acceptsArbitraryValues = $accepts; - - return $this; - } - - /** - * Configure whether field supports multiple values (e.g., multiple emails, phones) - */ - public function supportsMultiValue(bool $supports = true): self - { - $this->supportsMultiValue = $supports; - - return $this; - } - - /** - * Configure whether field supports unique value constraint per entity type - */ - public function supportsUniqueConstraint(bool $supports = true): self - { - $this->supportsUniqueConstraint = $supports; - - return $this; - } - - // ========== Data Type Specific Methods (from DataTypeConfigurators) ========== - - /** - * Enable encryption for this field (text fields) - */ - public function encrypted(): self - { - $this->encryptable(); - - return $this; - } - - /** - * Configure as a long text field (textarea) - */ - public function longText(): self - { - return $this; - } - - /** - * Allow users to create new options on the fly (choice fields) - */ - public function allowArbitraryValues(): self - { - $this->withArbitraryValues(); - - return $this; - } - - /** - * Field doesn't need user-configured options (choice fields) - * This disables database options UI and enables dynamic extraction from components - */ - public function withoutUserOptions(): self - { - $this->withoutUserOptions = true; - - return $this; - } - - /** - * Override the default visibility operators derived from the data type. - * - * @param array $operators - */ - public function visibilityOperators(array $operators): self - { - $this->visibilityOperators = $operators; - - return $this; - } - - /** - * Field requires lookup_type selection (entity type selector) - * This shows the entity selector directly without the options toggle - */ - public function requiresLookupType(bool $requires = true): self - { - $this->requiresLookupType = $requires; - - return $this; - } - - // ========== Validation Capability Methods ========== - - /** @param class-string ...$capabilityClasses */ - public function withValidationCapabilities(string ...$capabilityClasses): self - { - $this->validationCapabilities = [ - ...$this->validationCapabilities, - ...$capabilityClasses, - ]; - - return $this; - } - - /** @return array> */ - public function getValidationCapabilities(): array - { - return $this->validationCapabilities; - } - - // ========== Export Configuration ========== - - /** - * Get the field key - */ - public function getKey(): string - { - return $this->key; - } - - /** - * Get the field label - */ - public function getLabel(): string - { - return $this->label; - } - - /** - * Get the field icon - */ - public function getIcon(): string - { - return $this->icon; - } - /** * Get the data type for this configuration */ @@ -482,146 +131,6 @@ public function getDataType(): FieldDataType return $this->dataType; } - /** - * Get the form component - */ - public function getFormComponent(): string|Closure|null - { - return $this->formComponent; - } - - /** - * Get the priority - */ - public function getPriority(): int - { - return $this->priority; - } - - /** - * Get the default validation rules (always applied) - */ - public function getDefaultValidationRules(): array - { - return $this->defaultValidationRules; - } - - /** - * Get the default validation rules for individual items in multi-value fields. - * - * @return array - */ - public function getDefaultItemValidationRules(): array - { - return $this->defaultItemValidationRules; - } - - /** - * Check if field is searchable - */ - public function isSearchable(): bool - { - return $this->searchable; - } - - /** - * Check if field is sortable - */ - public function isSortable(): bool - { - return $this->sortable; - } - - /** - * Check if field is filterable - */ - public function isFilterable(): bool - { - return $this->filterable; - } - - /** - * Check if field is encryptable - */ - public function isEncryptable(): bool - { - return $this->encryptable; - } - - /** - * Check if field accepts arbitrary values - */ - public function acceptsArbitraryValues(): bool - { - return $this->acceptsArbitraryValues; - } - - public function withSettings(string $dataClass, string|Closure $schema): self - { - if (! is_subclass_of($dataClass, Data::class)) { - throw new InvalidArgumentException('Settings data class must extend '.Data::class); - } - - $this->settingsDataClass = $dataClass; - $this->settingsSchema = $schema; - - return $this; - } - - /** - * Set import example value for templates - */ - public function importExample(string $example): self - { - $this->importExample = $example; - - return $this; - } - - /** - * Set custom import column transformer - */ - public function importTransformer(Closure $transformer): self - { - $this->importTransformer = $transformer; - - return $this; - } - - /** - * Set custom export value transformer - */ - public function exportTransformer(Closure $transformer): self - { - $this->exportTransformer = $transformer; - - return $this; - } - - /** - * Get import example - */ - public function getImportExample(): ?string - { - return $this->importExample; - } - - /** - * Get import transformer - */ - public function getImportTransformer(): ?Closure - { - return $this->importTransformer; - } - - /** - * Get export transformer - */ - public function getExportTransformer(): ?Closure - { - return $this->exportTransformer; - } - public function data(): FieldTypeData { return new FieldTypeData( @@ -639,7 +148,9 @@ public function data(): FieldTypeData filterable: $this->filterable, encryptable: $this->encryptable, withoutUserOptions: $this->withoutUserOptions, - requiresLookupType: $this->requiresLookupType, + requiresRelationship: $this->requiresRelationship, + supportsPairing: $this->supportsPairing, + carriesOptionCategories: $this->carriesOptionCategories, acceptsArbitraryValues: $this->acceptsArbitraryValues, supportsMultiValue: $this->supportsMultiValue, supportsUniqueConstraint: $this->supportsUniqueConstraint, diff --git a/src/FieldTypeSystem/FieldTypeConfigurator.php b/src/FieldTypeSystem/FieldTypeConfigurator.php index 579467c9..c5fb8557 100644 --- a/src/FieldTypeSystem/FieldTypeConfigurator.php +++ b/src/FieldTypeSystem/FieldTypeConfigurator.php @@ -20,10 +20,13 @@ final class FieldTypeConfigurator private string $cacheStore = 'default'; + /** @var array */ private array $cacheTags = ['field-types', 'configuration']; + /** @var array */ private array $enabledFieldTypes = []; + /** @var array */ private array $disabledFieldTypes = []; private function __construct() @@ -51,6 +54,8 @@ public function discover(bool $enabled = true): self /** * Configure caching settings + * + * @param array $tags */ public function cache(bool $enabled = true, int $ttl = 3600, ?string $store = null, array $tags = []): self { @@ -82,6 +87,8 @@ public function when(bool $condition, Closure $callback): self /** * Enable only specific field types (empty array = all enabled) + * + * @param array $fieldTypes */ public function enabled(array $fieldTypes = []): self { @@ -92,6 +99,8 @@ public function enabled(array $fieldTypes = []): self /** * Disable specific field types + * + * @param array $fieldTypes */ public function disabled(array $fieldTypes = []): self { @@ -121,6 +130,8 @@ public function isFieldTypeAllowed(string $fieldTypeKey): bool /** * Restore the configurator from var_export + * + * @param array $properties */ public static function __set_state(array $properties): self { diff --git a/src/Filament/Integration/Base/AbstractFormComponent.php b/src/Filament/Integration/Base/AbstractFormComponent.php index 067b70c9..a9a4a20b 100644 --- a/src/Filament/Integration/Base/AbstractFormComponent.php +++ b/src/Filament/Integration/Base/AbstractFormComponent.php @@ -72,6 +72,10 @@ public function make(CustomField $customField, array $dependentFieldCodes = [], return $this->configure($field, $customField, $allFields, $dependentFieldCodes, $record); } + /** + * @param Collection $allFields + * @param array $dependentFieldCodes + */ protected function configure( Field $field, CustomField $customField, @@ -105,10 +109,13 @@ function (Field $field) use ($customField): Field { ) ) ) + // Empty is a value: a field the user cleared has to reach the writer, or a + // relationship could never lose its last record. A clear is destructive, so it + // travels only on a field the server can prove the form showed. ->dehydrated( - fn (mixed $state): bool => ! FeatureManager::isEnabled(CustomFieldsFeature::FIELD_CONDITIONAL_VISIBILITY) || - $this->coreVisibilityLogic->shouldAlwaysSave($customField) || - filled($state) + fn (Get $get, mixed $state): bool => $this->coreVisibilityLogic->shouldAlwaysSave($customField) || + filled($state) || + $this->isProvablyVisible($customField, $allFields, $get) ) ->when( $this->validationService->isRequired($customField) && $customField->typeData->dataType->isBoolean(), @@ -229,6 +236,36 @@ private function isVisibleForValidation( Collection $allFields, Get $get ): bool { + return $this->reproducedVisibility($customField, $allFields, $get) ?? true; + } + + /** + * Whether the server can PROVE the form is showing the field. Dehydration resolves an + * unreproducible expression the other way from validation: a redundant validation costs a + * user one more click, while dehydrating a state the form never showed writes an empty + * value over a stored one. + * + * @param Collection $allFields + */ + private function isProvablyVisible( + CustomField $customField, + Collection $allFields, + Get $get + ): bool { + return $this->reproducedVisibility($customField, $allFields, $get) === true; + } + + /** + * The field's own conditions evaluated against live form state, or null when the server + * cannot reproduce the expression the client renders (see canReproduceClientVisibility). + * + * @param Collection $allFields + */ + private function reproducedVisibility( + CustomField $customField, + Collection $allFields, + Get $get + ): ?bool { if (! FeatureManager::isEnabled(CustomFieldsFeature::FIELD_CONDITIONAL_VISIBILITY)) { return true; } @@ -240,7 +277,7 @@ private function isVisibleForValidation( $visibility = $this->coreVisibilityLogic->getVisibilityData($customField); if (! $this->canReproduceClientVisibility($visibility, $allFields)) { - return true; + return null; } $rawValues = $get('custom_fields'); @@ -306,6 +343,9 @@ private function canReproduceClientVisibility(VisibilityData $visibility, Collec return true; } + /** + * @param Collection $allFields + */ private function applyVisibility( Field $field, CustomField $customField, @@ -344,6 +384,8 @@ protected function getFieldValidationRules(CustomField $customField, string|int| /** * Apply settings dynamically to any Filament component + * + * @param array $settings */ protected function applySettingsToComponent(Field $component, array $settings): Field { diff --git a/src/Filament/Integration/Base/AbstractInfolistEntry.php b/src/Filament/Integration/Base/AbstractInfolistEntry.php index 62129191..c503429e 100644 --- a/src/Filament/Integration/Base/AbstractInfolistEntry.php +++ b/src/Filament/Integration/Base/AbstractInfolistEntry.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Base; use Filament\Infolists\Components\Entry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Contracts\InfolistComponentInterface; use Relaticle\CustomFields\Models\CustomField; @@ -17,5 +18,5 @@ abstract class AbstractInfolistEntry implements InfolistComponentInterface /** * Create and configure an infolist entry. */ - abstract public function make(CustomField $customField): Entry; + abstract public function make(CustomField $customField, ?Model $record = null): Entry; } diff --git a/src/Filament/Integration/Base/AbstractTableColumn.php b/src/Filament/Integration/Base/AbstractTableColumn.php index 412eccb1..2352ccbe 100644 --- a/src/Filament/Integration/Base/AbstractTableColumn.php +++ b/src/Filament/Integration/Base/AbstractTableColumn.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Base; use Filament\Tables\Columns\Column as BaseColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Contracts\TableColumnInterface; use Relaticle\CustomFields\Models\CustomField; @@ -17,5 +18,5 @@ abstract class AbstractTableColumn implements TableColumnInterface /** * Create and configure a table column. */ - abstract public function make(CustomField $customField): BaseColumn; + abstract public function make(CustomField $customField, ?Model $record = null): BaseColumn; } diff --git a/src/Filament/Integration/Base/AbstractTableFilter.php b/src/Filament/Integration/Base/AbstractTableFilter.php index d280a8c9..29db27a4 100644 --- a/src/Filament/Integration/Base/AbstractTableFilter.php +++ b/src/Filament/Integration/Base/AbstractTableFilter.php @@ -4,11 +4,15 @@ namespace Relaticle\CustomFields\Filament\Integration\Base; +use Closure; use Filament\Tables\Filters\BaseFilter; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Contracts\TableFilterInterface; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Support\ThroughRelationResolver; /** * ABOUTME: Abstract base class for table filter components providing common structure. @@ -19,7 +23,24 @@ abstract class AbstractTableFilter implements TableFilterInterface /** * Create and configure a table filter. */ - abstract public function make(CustomField $customField): BaseFilter; + abstract public function make(CustomField $customField, ?Model $record = null, ?string $through = null): BaseFilter; + + /** + * Run a constraint written against the model that owns the field. Without a through path + * that is the row model itself, so both cases share one query shape. + * + * @param Builder $query + * @param Closure(Builder): Builder $constraint + * @return Builder + */ + protected function constrainThrough(Builder $query, ?string $through, Closure $constraint): Builder + { + if ($through === null) { + return $constraint($query); + } + + return app(ThroughRelationResolver::class)->constrain($query, $through, $constraint); + } protected function hasColorOptionsEnabled(CustomField $customField): bool { diff --git a/src/Filament/Integration/Builders/BaseBuilder.php b/src/Filament/Integration/Builders/BaseBuilder.php index 689b6254..a8e8d1b3 100644 --- a/src/Filament/Integration/Builders/BaseBuilder.php +++ b/src/Filament/Integration/Builders/BaseBuilder.php @@ -23,10 +23,13 @@ abstract class BaseBuilder protected Model|string|null $explicitModel = null; + /** @var ?Builder */ protected ?Builder $sections = null; + /** @var array */ protected array $except = []; + /** @var array */ protected array $only = []; /** @var array */ @@ -71,6 +74,9 @@ public function forModel(Model|string $model): static return $this; } + /** + * @param array $fieldCodes + */ public function except(array $fieldCodes): static { $this->except = $fieldCodes; @@ -78,6 +84,9 @@ public function except(array $fieldCodes): static return $this; } + /** + * @param array $fieldCodes + */ public function only(array $fieldCodes): static { $this->only = $fieldCodes; diff --git a/src/Filament/Integration/Builders/ExporterBuilder.php b/src/Filament/Integration/Builders/ExporterBuilder.php index 5dcd5068..51d79a28 100644 --- a/src/Filament/Integration/Builders/ExporterBuilder.php +++ b/src/Filament/Integration/Builders/ExporterBuilder.php @@ -7,10 +7,11 @@ namespace Relaticle\CustomFields\Filament\Integration\Builders; +use Filament\Actions\Exports\ExportColumn; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\Container\CircularDependencyException; use Illuminate\Support\Collection; -use Relaticle\CustomFields\Contracts\ValueResolvers; +use Relaticle\CustomFields\Contracts\ValueResolverInterface; use Relaticle\CustomFields\Facades\CustomFieldsType; use Relaticle\CustomFields\Filament\Integration\Factories\ExportColumnFactory; use Relaticle\CustomFields\Models\CustomField; @@ -19,6 +20,8 @@ final class ExporterBuilder extends BaseBuilder { /** + * @return Collection + * * @throws BindingResolutionException * @throws CircularDependencyException */ @@ -42,7 +45,7 @@ public function columns(): Collection return null; // Don't export values for hidden fields } - $valueResolver = app(ValueResolvers::class); + $valueResolver = app(ValueResolverInterface::class); $value = $valueResolver->resolve( record: $record, diff --git a/src/Filament/Integration/Builders/FormBuilder.php b/src/Filament/Integration/Builders/FormBuilder.php index 62bf6e46..98b4c9e6 100644 --- a/src/Filament/Integration/Builders/FormBuilder.php +++ b/src/Filament/Integration/Builders/FormBuilder.php @@ -1,11 +1,13 @@ forModel($this->explicitModel ?? null) @@ -42,6 +44,10 @@ public function withoutSections(bool $withoutSections = true): static return $this; } + /** + * @param Collection $fields + * @return array + */ private function getDependentFieldCodes(Collection $fields): array { $service = app(CoreVisibilityLogicService::class); @@ -67,6 +73,9 @@ private function getDependentFieldCodes(Collection $fields): array return array_unique($dependentCodes); } + /** + * @return Collection + */ public function values(): Collection { $fieldComponentFactory = app(FieldComponentFactory::class); @@ -79,7 +88,7 @@ public function values(): Collection // Resolve record for visibility (null for create forms — fail-open) $record = isset($this->model) && $this->model->exists ? $this->model : null; - $createField = fn (CustomField $customField) => $fieldComponentFactory->create( + $createField = fn (CustomField $customField): Component => $fieldComponentFactory->create( $customField, $dependentFieldCodes, $allFields, @@ -93,7 +102,7 @@ public function values(): Collection } return $this->getFilteredSections() - ->map(function (CustomFieldSection $section) use ($sectionComponentFactory, $createField, $allFields, $record) { + ->map(function (CustomFieldSection $section) use ($sectionComponentFactory, $createField, $allFields, $record): ?Component { $fields = $section->fields->map($createField); return $fields->isEmpty() diff --git a/src/Filament/Integration/Builders/FormContainer.php b/src/Filament/Integration/Builders/FormContainer.php index 66c1c1f4..72182da2 100644 --- a/src/Filament/Integration/Builders/FormContainer.php +++ b/src/Filament/Integration/Builders/FormContainer.php @@ -1,7 +1,10 @@ */ private array $except = []; + /** @var array */ private array $only = []; /** @var array */ @@ -41,6 +46,9 @@ public function forModel(Model|string|null $model): static return $this; } + /** + * @param array $fieldCodes + */ public function except(array $fieldCodes): static { $this->except = $fieldCodes; @@ -48,6 +56,9 @@ public function except(array $fieldCodes): static return $this; } + /** + * @param array $fieldCodes + */ public function only(array $fieldCodes): static { $this->only = $fieldCodes; @@ -72,6 +83,9 @@ public function withoutSections(bool $withoutSections = true): static return $this; } + /** + * @return array + */ private function generateSchema(): array { // Inline priority: explicit ?? record ?? model class diff --git a/src/Filament/Integration/Builders/ImporterBuilder.php b/src/Filament/Integration/Builders/ImporterBuilder.php index 011bda23..b7904e5f 100644 --- a/src/Filament/Integration/Builders/ImporterBuilder.php +++ b/src/Filament/Integration/Builders/ImporterBuilder.php @@ -13,6 +13,9 @@ final class ImporterBuilder extends BaseBuilder { + /** + * @return Collection + */ public function columns(): Collection { return $this->getAllFields() @@ -128,6 +131,10 @@ public function saveValues(?Model $tenant = null): void }); } + /** + * @param array $data + * @return array + */ public function filterCustomFieldsFromData(array $data): array { return array_filter( diff --git a/src/Filament/Integration/Builders/InfolistContainer.php b/src/Filament/Integration/Builders/InfolistContainer.php index 7f7a37d8..1da05ab9 100644 --- a/src/Filament/Integration/Builders/InfolistContainer.php +++ b/src/Filament/Integration/Builders/InfolistContainer.php @@ -1,5 +1,7 @@ */ private array $except = []; + /** @var array */ private array $only = []; /** @var array */ @@ -46,6 +50,9 @@ public function forModel(Model|string|null $model): static return $this; } + /** + * @param array $fieldCodes + */ public function except(array $fieldCodes): static { $this->except = $fieldCodes; @@ -53,6 +60,9 @@ public function except(array $fieldCodes): static return $this; } + /** + * @param array $fieldCodes + */ public function only(array $fieldCodes): static { $this->only = $fieldCodes; diff --git a/src/Filament/Integration/Builders/TableBuilder.php b/src/Filament/Integration/Builders/TableBuilder.php index 3b9e73bf..439cd4be 100644 --- a/src/Filament/Integration/Builders/TableBuilder.php +++ b/src/Filament/Integration/Builders/TableBuilder.php @@ -8,16 +8,39 @@ namespace Relaticle\CustomFields\Filament\Integration\Builders; use Closure; +use Filament\Tables\Columns\Column; +use Filament\Tables\Filters\BaseFilter; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\FeatureSystem\FeatureManager; +use Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns\RecordColumnView; use Relaticle\CustomFields\Filament\Integration\Factories\FieldColumnFactory; use Relaticle\CustomFields\Filament\Integration\Factories\FieldFilterFactory; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\QueryBuilders\ColumnSearchableQuery; use Relaticle\CustomFields\Services\Visibility\BackendVisibilityService; +use Relaticle\CustomFields\Support\ThroughRelationResolver; final class TableBuilder extends BaseBuilder { + private ?string $through = null; + + /** + * Read the fields of a to-one related record instead of the row record, for tables whose + * rows carry no custom fields of their own. + */ + public function through(string $relation): static + { + $this->through = $relation; + + return $this; + } + + /** + * @return Collection + */ public function columns(): Collection { if (! FeatureManager::isEnabled(CustomFieldsFeature::UI_TABLE_COLUMNS)) { @@ -32,24 +55,35 @@ public function columns(): Collection return $allFields ->filter(fn (CustomField $field): bool => $field->typeData->tableColumn !== null) - ->map(function (CustomField $field) use ($fieldColumnFactory, $backendVisibilityService, $allFields) { + ->map(function (CustomField $field) use ($fieldColumnFactory, $backendVisibilityService, $allFields): Column { $column = $fieldColumnFactory->create($field); + $this->readThroughRelation($column, $field); + + $isVisible = fn (mixed $record): bool => ($subject = $this->fieldRecord($record)) instanceof Model + && $backendVisibilityService->isFieldVisible($subject, $field, $allFields); + + // A column that renders from the record instead of the state never reaches a + // formatter, so its cell answers the same condition where it is built. + if ($column instanceof RecordColumnView) { + return $column->renderFor($isVisible); + } + if (! method_exists($column, 'formatStateUsing')) { return $column; } $existingFormatter = (fn (): ?Closure => $this->formatStateUsing)->call($column); // @phpstan-ignore property.notFound - $column->formatStateUsing(function (mixed $state, mixed $record) use ($field, $backendVisibilityService, $allFields, $existingFormatter, $column): mixed { - if (! $backendVisibilityService->isFieldVisible($record, $field, $allFields)) { + $column->formatStateUsing(function (mixed $state, mixed $record) use ($isVisible, $existingFormatter, $column): mixed { + if (! $isVisible($record)) { return null; } if ($existingFormatter) { return $column->evaluate($existingFormatter, [ 'state' => $state, - 'record' => $record, + 'record' => $this->fieldRecord($record), 'column' => $column, ]); } @@ -62,6 +96,9 @@ public function columns(): Collection ->values(); } + /** + * @return Collection + */ public function filters(): Collection { if (! FeatureManager::isEnabled(CustomFieldsFeature::UI_TABLE_FILTERS)) { @@ -72,8 +109,72 @@ public function filters(): Collection return $this->getAllFields() ->filter(fn (CustomField $field): bool => $field->isFilterable() && $field->typeData->tableFilter !== null) - ->map(fn (CustomField $field) => $fieldFilterFactory->create($field)) + ->map(fn (CustomField $field) => $fieldFilterFactory->create($field, $this->through)) ->filter() ->values(); } + + /** + * Point a factory-built column at the related record. Every setter here is idempotent, + * so this replaces what the field type configured instead of layering onto it. + */ + private function readThroughRelation(Column $column, CustomField $field): void + { + if ($this->through === null) { + return; + } + + $relation = $this->through; + $resolver = app(ThroughRelationResolver::class); + + $column->getStateUsing( + fn (Model $record): mixed => $resolver->relatedRecord($record, $relation)?->getCustomFieldValue($field) + ); + + if ($column instanceof RecordColumnView) { + // Ordering a record field through a relation would join the link ledger a second + // time, so the column stays readable and filterable instead of ordering by nothing. + $column->through($relation)->sortable(false); + } elseif ($column->isSortable()) { + $column->sortable( + condition: true, + query: fn (Builder $query, string $direction): Builder => $resolver->orderByFieldValue($query, $relation, $field, $direction), + ); + } + + if (! $column->isSearchable()) { + return; + } + + // The column type already knows how to search its own field; the path only decides + // which record is asked, so its query is re-run against the related model. + $existingSearch = (fn (): ?Closure => $this->searchQuery)->call($column); // @phpstan-ignore property.notFound + + $column->searchable( + condition: true, + query: fn (Builder $query, string $search): Builder => $resolver->constrain( + $query, + $relation, + fn (Builder $related): mixed => $existingSearch instanceof Closure + ? $column->evaluate($existingSearch, ['query' => $related, 'search' => $search, 'searchQuery' => $search]) + : (new ColumnSearchableQuery)->builder($related, $field, $search), + ), + ); + } + + /** + * The record a column's state, visibility and formatter are evaluated against. + */ + private function fieldRecord(mixed $record): ?Model + { + if (! $record instanceof Model) { + return null; + } + + if ($this->through === null) { + return $record; + } + + return app(ThroughRelationResolver::class)->relatedRecord($record, $this->through); + } } diff --git a/src/Filament/Integration/Components/Forms/FileUploadComponent.php b/src/Filament/Integration/Components/Forms/FileUploadComponent.php index 82aff9c8..e9679b15 100644 --- a/src/Filament/Integration/Components/Forms/FileUploadComponent.php +++ b/src/Filament/Integration/Components/Forms/FileUploadComponent.php @@ -22,6 +22,9 @@ public function create(CustomField $customField): Field return $this->applySettingsToComponent($component, $defaults); } + /** + * @return array + */ private function getSmartDefaults(): array { return [ diff --git a/src/Filament/Integration/Components/Forms/LinkComponent.php b/src/Filament/Integration/Components/Forms/LinkComponent.php index 60ebbf93..16dec8bd 100644 --- a/src/Filament/Integration/Components/Forms/LinkComponent.php +++ b/src/Filament/Integration/Components/Forms/LinkComponent.php @@ -4,6 +4,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Forms; +use Illuminate\Support\Arr; use Relaticle\CustomFields\FieldTypeSystem\FieldManager; use Relaticle\CustomFields\Filament\Integration\Base\AbstractFormComponent; use Relaticle\CustomFields\Filament\Integration\Components\Forms\MultiValueInput\MultiValueInputComponent; @@ -28,7 +29,7 @@ public function create(CustomField $customField): MultiValueInputComponent ->placeholder(__('custom-fields::custom-fields.link.add_link_placeholder')) ->nestedRecursiveRules(['max:2048', 'regex:/^(https?:\/\/)?([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(\/.*)?$/']) ->rules(['array', 'max:'.$maxValues]) - ->dehydrateStateUsing(fn (mixed $state): array => collect($state) + ->dehydrateStateUsing(fn (mixed $state): array => collect(Arr::wrap($state)) ->map(fn (mixed $v): string => $fieldType ? $fieldType->setValue(trim((string) $v)) : trim((string) $v)) diff --git a/src/Filament/Integration/Components/Forms/NumberComponent.php b/src/Filament/Integration/Components/Forms/NumberComponent.php index 4dbbe564..1cb9ec2d 100644 --- a/src/Filament/Integration/Components/Forms/NumberComponent.php +++ b/src/Filament/Integration/Components/Forms/NumberComponent.php @@ -14,8 +14,6 @@ public function create(CustomField $customField): TextInput { return TextInput::make($customField->getFieldName()) ->numeric() - ->placeholder(null) - ->minValue($customField->settings->min ?? null) - ->maxValue($customField->settings->max ?? null); + ->placeholder(null); } } diff --git a/src/Filament/Integration/Components/Forms/RecordSelectComponent.php b/src/Filament/Integration/Components/Forms/RecordSelectComponent.php index 2e851c88..3f2ea950 100644 --- a/src/Filament/Integration/Components/Forms/RecordSelectComponent.php +++ b/src/Filament/Integration/Components/Forms/RecordSelectComponent.php @@ -6,23 +6,18 @@ use Relaticle\CustomFields\Filament\Integration\Base\AbstractFormComponent; use Relaticle\CustomFields\Filament\Integration\Components\Forms\RecordSelectInput\RecordSelectInputComponent; +use Relaticle\CustomFields\Filament\Integration\Concerns\Forms\ConfiguresRecordSelects; use Relaticle\CustomFields\Models\CustomField; final readonly class RecordSelectComponent extends AbstractFormComponent { - private const MAX_MULTIPLE_RECORDS = 100; + use ConfiguresRecordSelects; public function create(CustomField $customField): RecordSelectInputComponent { - $allowMultiple = $customField->settings->allow_multiple ?? false; - $maxValues = $allowMultiple ? self::MAX_MULTIPLE_RECORDS : 1; - - return RecordSelectInputComponent::make($customField->getFieldName()) - ->lookupType($customField->lookup_type) - ->allowMultiple($allowMultiple) - ->maxValues($maxValues) - ->placeholder(__('custom-fields::custom-fields.record.search_placeholder')) - ->addLabel(__('custom-fields::custom-fields.record.add_record_placeholder')) - ->rules(['array', 'max:'.$maxValues]); + return $this->configureRecordSelect( + RecordSelectInputComponent::make($customField->getFieldName()), + $customField, + ); } } diff --git a/src/Filament/Integration/Components/Forms/RecordSelectInput/RecordSelectInputComponent.php b/src/Filament/Integration/Components/Forms/RecordSelectInput/RecordSelectInputComponent.php index f6011196..5dd1f6b3 100644 --- a/src/Filament/Integration/Components/Forms/RecordSelectInput/RecordSelectInputComponent.php +++ b/src/Filament/Integration/Components/Forms/RecordSelectInput/RecordSelectInputComponent.php @@ -13,20 +13,21 @@ use Filament\Support\Concerns\HasExtraAlpineAttributes; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\App; use Livewire\Attributes\Renderless; use Relaticle\CustomFields\Data\AvatarConfiguration; use Relaticle\CustomFields\Data\EntityConfigurationData; +use Relaticle\CustomFields\Data\RecordLinkPayload; use Relaticle\CustomFields\Facades\Entities; -use Relaticle\CustomFields\Support\Utils; -use Throwable; +use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\QueryBuilders\EntitySearchQuery; /** - * A custom Filament form field for selecting records from other entities - * with search, avatars, and single/multiple mode support. + * The one-way record field's input: a searchable select with avatars, showing one record or a + * row of removable pills. It is the same control in every flavor, because a flavor decides how + * a surface looks and this type has only ever had the one look. * - * Single value: Shows a searchable select dropdown - * Multiple values: Shows pills + add button with searchable dropdown + * The paired type extends it with what pairing adds: chips, the inline move confirmation, and + * provenance on hover. */ class RecordSelectInputComponent extends Field implements HasNestedRecursiveValidationRulesContract { @@ -48,6 +49,8 @@ class RecordSelectInputComponent extends Field implements HasNestedRecursiveVali protected int|Closure $maxVisiblePills = 3; + protected ?CustomField $customField = null; + protected function setUp(): void { parent::setUp(); @@ -74,11 +77,47 @@ protected function setUp(): void return $state !== null ? [$state] : []; } - // Filter out empty values - return array_values(array_filter($state, fn (mixed $value): bool => filled($value))); + // The map form carries the confirmation the writer needs before it takes a record + // from its holder, so it travels whole; only its ids are cleaned. + if (array_key_exists('ids', $state)) { + $ids = self::filledIds($state['ids']); + + return [ + 'ids' => $ids, + 'confirmed' => RecordLinkPayload::confirmedIds($state, $ids), + ]; + } + + return self::filledIds($state); }); } + /** + * @return array + */ + private static function filledIds(mixed $ids): array + { + return is_array($ids) + ? array_values(array_filter($ids, fn (mixed $value): bool => filled($value))) + : []; + } + + /** + * The field the picker writes, so it can ask the same guard the writer asks before it + * offers to move a record away from whoever holds it. + */ + public function customField(?CustomField $customField): static + { + $this->customField = $customField; + + return $this; + } + + public function getCustomField(): ?CustomField + { + return $this->customField; + } + public function allowMultiple(bool|Closure $allow = true): static { $this->allowMultiple = $allow; @@ -159,7 +198,7 @@ public function getMaxVisiblePills(): int */ public function getMinSearchLength(): int { - return (int) config('custom-fields.selects.record_lookup.min_search_length', 2); + return (int) config('custom-fields.selects.record.min_search_length', 2); } /** @@ -179,7 +218,7 @@ public function getEntityConfiguration(): ?EntityConfigurationData /** * Prepare entity query with common attributes. * - * @return array{entity: EntityConfigurationData, model: Model, query: Builder, keyName: string, titleAttribute: string, avatarConfig: ?AvatarConfiguration}|null + * @return array{entity: EntityConfigurationData, model: Model, query: Builder, keyName: string, titleAttribute: string, avatarConfig: ?AvatarConfiguration}|null */ private function prepareEntityQuery(): ?array { @@ -216,11 +255,14 @@ private function prepareEntityQuery(): ?array * resolved without a schema query: a runtime Schema::hasColumn() call would be * a per-request round trip. The one exception is the documented 'updated_at', * which falls back to the key on a model that opts out of timestamps. + * + * @param Builder $query + * @return Builder */ private function applyLookupOrder(Builder $query, Model $model): Builder { - $column = config('custom-fields.selects.record_lookup.order_column'); - $direction = (string) config('custom-fields.selects.record_lookup.order_direction', 'desc'); + $column = config('custom-fields.selects.record.order_column'); + $direction = (string) config('custom-fields.selects.record.order_direction', 'desc'); $key = $model->getQualifiedKeyName(); if (! is_string($column) || $column === '') { @@ -238,13 +280,13 @@ private function applyLookupOrder(Builder $query, Model $model): Builder private function lookupLimit(): int { - return (int) config('custom-fields.selects.record_lookup.limit', 50); + return (int) config('custom-fields.selects.record.limit', 50); } /** * Search for records matching the query. * - * @return array + * @return array */ public function searchRecords(string $search): array { @@ -257,30 +299,11 @@ public function searchRecords(string $search): array ['entity' => $entity, 'model' => $model, 'query' => $query, 'keyName' => $keyName, 'titleAttribute' => $titleAttribute, 'avatarConfig' => $avatarConfig] = $prepared; $searchAttributes = $entity->getSearchAttributes(); - // Try to use resource's search if available - $resource = null; - if ($entity->getResourceClass()) { - try { - $resource = App::make($entity->getResourceClass()); - } catch (Throwable) { - $resource = null; - } + if ($searchAttributes === []) { + $searchAttributes = [$titleAttribute]; } - if ($resource !== null) { - Utils::invokeMethodByReflection($resource, 'applyGlobalSearchAttributeConstraints', [ - $query, - $search, - $searchAttributes, - ]); - } else { - $query->where(function (Builder $q) use ($search, $searchAttributes, $titleAttribute): void { - $attrs = $searchAttributes === [] ? [$titleAttribute] : $searchAttributes; - foreach ($attrs as $attribute) { - $q->orWhere($attribute, 'like', sprintf('%%%s%%', $search)); - } - }); - } + $query = app(EntitySearchQuery::class)->apply($query, $search, $searchAttributes, $entity->getResourceClass()); $records = $this->applyLookupOrder($query, $model) ->limit($this->lookupLimit()) @@ -293,7 +316,7 @@ public function searchRecords(string $search): array * Get records by their IDs. * * @param array $ids - * @return array + * @return array */ public function getRecordsByIds(array $ids): array { @@ -312,13 +335,33 @@ public function getRecordsByIds(array $ids): array $records = $query->whereIn($keyName, $ids)->get() ->sortBy(fn (Model $record): int|false => array_search($record->getKey(), $ids, true)); - return $this->formatRecordsForJs($records, $keyName, $titleAttribute, $avatarConfig); + return $this->formatRecordsForJs($records, $keyName, $titleAttribute, $avatarConfig, $this->provenance()); + } + + /** + * Where each selected record's link came from, for the surfaces with somewhere to show it. + * A plain select draws no chip, so it says nothing. + * + * @return array + */ + protected function provenance(): array + { + return []; + } + + /** + * Whether taking a record could take it away from another holder. The record type never + * offers that move, so its select never asks. + */ + public function checksHolderConflicts(): bool + { + return false; } /** * Get initial options (first 50 records). * - * @return array + * @return array */ public function getInitialOptions(): array { @@ -340,13 +383,16 @@ public function getInitialOptions(): array /** * Format records for JavaScript consumption. * - * @return array + * @param iterable $records + * @param array $provenance + * @return array */ private function formatRecordsForJs( iterable $records, string $keyName, string $titleAttribute, - ?AvatarConfiguration $avatarConfig + ?AvatarConfiguration $avatarConfig, + array $provenance = [] ): array { $result = []; @@ -357,6 +403,7 @@ private function formatRecordsForJs( 'label' => $record->getAttribute($titleAttribute) ?? '', 'avatar' => $this->getAvatarUrl($record, $avatarConfig), 'avatarShape' => $avatarConfig?->getCssClass() ?? 'rounded-full', + 'provenance' => $provenance[$id] ?? null, ]; } @@ -373,10 +420,9 @@ private function getAvatarUrl(Model $record, ?AvatarConfiguration $avatarConfig) } /** - * Search records via Livewire call. - * Called from Alpine.js when user types in search box. + * Search records via Livewire call, from Alpine when the user types in the search box. * - * @return array + * @return array */ #[ExposedLivewireMethod] #[Renderless] diff --git a/src/Filament/Integration/Components/Forms/RelationshipPicker/RelationshipPickerComponent.php b/src/Filament/Integration/Components/Forms/RelationshipPicker/RelationshipPickerComponent.php new file mode 100644 index 00000000..6182d4d8 --- /dev/null +++ b/src/Filament/Integration/Components/Forms/RelationshipPicker/RelationshipPickerComponent.php @@ -0,0 +1,135 @@ +view($polishedView); + } + } + + /** + * Where each selected link came from, for the chip's hover. Candidates in the dropdown are + * not linked yet, so only the selected records carry it. + * + * @return array + */ + protected function provenance(): array + { + $customField = $this->getCustomField(); + + // A component built outside a schema, as the import path does, has no record to read + // provenance from and asking for one would fail before it could say so. + if (! $customField instanceof CustomField || ! isset($this->container)) { + return []; + } + + $record = $this->getRecord(); + + if (! $record instanceof Model) { + return []; + } + + $record->loadMissing(['outgoingLinks.createdBy', 'incomingLinks.createdBy']); + + return app(RecordChips::class)->provenance($record, $customField); + } + + /** + * The page that creates a record of the target entity, or null when the host registered no + * resource with one: the picker offers create-new only where it can land somewhere. + */ + public function getCreateUrl(): ?string + { + $entity = $this->getEntityConfiguration(); + + return $entity instanceof EntityConfigurationData + ? app(RecordChips::class)->createUrl($entity) + : null; + } + + public function getCreateLabel(): ?string + { + $entity = $this->getEntityConfiguration(); + + return $entity instanceof EntityConfigurationData + ? __('custom-fields::custom-fields.record.create_new', ['entity' => $entity->getLabelSingular()]) + : null; + } + + /** + * Whether taking a record could take it away from another holder. A relationship whose far + * end holds many never can, so the picker skips the round trip that asks. + */ + public function checksHolderConflicts(): bool + { + $customField = $this->getCustomField(); + $definition = $customField?->relationshipDefinition(); + + if (! $customField instanceof CustomField || ! $definition instanceof CustomFieldRelationship) { + return false; + } + + $write = $definition->writeDirectionFor($customField); + + return app(CardinalityGuard::class)->endHoldsOne( + $definition, + $write === CustomFieldRelationship::DIRECTION_FROM + ? CustomFieldRelationship::DIRECTION_TO + : CustomFieldRelationship::DIRECTION_FROM, + ); + } + + /** + * What the writer would refuse for one candidate, so the picker can confirm the move + * inline instead of failing on save. The guard is the single source of that sentence. + */ + #[ExposedLivewireMethod] + #[Renderless] + public function holderConflictFor(string $recordId): ?string + { + $customField = $this->getCustomField(); + $definition = $customField?->relationshipDefinition(); + + if (! $customField instanceof CustomField || ! $definition instanceof CustomFieldRelationship) { + return null; + } + + $violations = app(CardinalityGuard::class)->violations( + $definition, + $definition->writeDirectionFor($customField), + $this->getRecord()?->getKey(), + [$recordId], + ); + + return $violations[0] ?? null; + } +} diff --git a/src/Filament/Integration/Components/Forms/RelationshipSelectComponent.php b/src/Filament/Integration/Components/Forms/RelationshipSelectComponent.php new file mode 100644 index 00000000..d6586cf1 --- /dev/null +++ b/src/Filament/Integration/Components/Forms/RelationshipSelectComponent.php @@ -0,0 +1,23 @@ +configureRecordSelect( + RelationshipPickerComponent::make($customField->getFieldName()), + $customField, + ); + } +} diff --git a/src/Filament/Integration/Components/Infolists/BooleanEntry.php b/src/Filament/Integration/Components/Infolists/BooleanEntry.php index 27575297..e80b4a65 100644 --- a/src/Filament/Integration/Components/Infolists/BooleanEntry.php +++ b/src/Filament/Integration/Components/Infolists/BooleanEntry.php @@ -5,12 +5,13 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Infolists; use Filament\Infolists\Components\IconEntry as BaseIconEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Models\CustomField; final class BooleanEntry extends AbstractInfolistEntry { - public function make(CustomField $customField): BaseIconEntry + public function make(CustomField $customField, ?Model $record = null): BaseIconEntry { return BaseIconEntry::make($customField->getFieldName()) ->boolean() diff --git a/src/Filament/Integration/Components/Infolists/ColorEntry.php b/src/Filament/Integration/Components/Infolists/ColorEntry.php index 3058e6f8..a07f2f71 100644 --- a/src/Filament/Integration/Components/Infolists/ColorEntry.php +++ b/src/Filament/Integration/Components/Infolists/ColorEntry.php @@ -5,12 +5,13 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Infolists; use Filament\Infolists\Components\ColorEntry as BaseColorEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Models\CustomField; final class ColorEntry extends AbstractInfolistEntry { - public function make(CustomField $customField): BaseColorEntry + public function make(CustomField $customField, ?Model $record = null): BaseColorEntry { return BaseColorEntry::make($customField->getFieldName()) ->label($customField->name) diff --git a/src/Filament/Integration/Components/Infolists/CurrencyEntry.php b/src/Filament/Integration/Components/Infolists/CurrencyEntry.php index 70b9f9aa..9bfc946b 100644 --- a/src/Filament/Integration/Components/Infolists/CurrencyEntry.php +++ b/src/Filament/Integration/Components/Infolists/CurrencyEntry.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Infolists; use Filament\Infolists\Components\TextEntry as BaseTextEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Filament\Integration\Concerns\Shared\ConfiguresCurrencyFormatting; use Relaticle\CustomFields\Models\CustomField; @@ -13,7 +14,7 @@ final class CurrencyEntry extends AbstractInfolistEntry { use ConfiguresCurrencyFormatting; - public function make(CustomField $customField): BaseTextEntry + public function make(CustomField $customField, ?Model $record = null): BaseTextEntry { $entry = BaseTextEntry::make($customField->getFieldName()) ->label($customField->name) diff --git a/src/Filament/Integration/Components/Infolists/DateTimeEntry.php b/src/Filament/Integration/Components/Infolists/DateTimeEntry.php index db2003b3..8324efc3 100644 --- a/src/Filament/Integration/Components/Infolists/DateTimeEntry.php +++ b/src/Filament/Integration/Components/Infolists/DateTimeEntry.php @@ -5,13 +5,14 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Infolists; use Filament\Infolists\Components\TextEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\CustomFields; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Models\CustomField; final class DateTimeEntry extends AbstractInfolistEntry { - public function make(CustomField $customField): TextEntry + public function make(CustomField $customField, ?Model $record = null): TextEntry { $isDateTime = $customField->isDateTimeField(); diff --git a/src/Filament/Integration/Components/Infolists/EmailEntry.php b/src/Filament/Integration/Components/Infolists/EmailEntry.php index 1f6babc2..0c976d72 100644 --- a/src/Filament/Integration/Components/Infolists/EmailEntry.php +++ b/src/Filament/Integration/Components/Infolists/EmailEntry.php @@ -5,12 +5,13 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Infolists; use Filament\Infolists\Components\ViewEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Models\CustomField; final class EmailEntry extends AbstractInfolistEntry { - public function make(CustomField $customField): ViewEntry + public function make(CustomField $customField, ?Model $record = null): ViewEntry { return ViewEntry::make($customField->getFieldName()) ->label($customField->name) diff --git a/src/Filament/Integration/Components/Infolists/HtmlEntry.php b/src/Filament/Integration/Components/Infolists/HtmlEntry.php index c859d366..397cd45b 100644 --- a/src/Filament/Integration/Components/Infolists/HtmlEntry.php +++ b/src/Filament/Integration/Components/Infolists/HtmlEntry.php @@ -5,12 +5,13 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Infolists; use Filament\Infolists\Components\TextEntry as BaseTextEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Models\CustomField; final class HtmlEntry extends AbstractInfolistEntry { - public function make(CustomField $customField): BaseTextEntry + public function make(CustomField $customField, ?Model $record = null): BaseTextEntry { return BaseTextEntry::make($customField->getFieldName()) ->html() diff --git a/src/Filament/Integration/Components/Infolists/LinkEntry.php b/src/Filament/Integration/Components/Infolists/LinkEntry.php index 12d1c882..c90ba701 100644 --- a/src/Filament/Integration/Components/Infolists/LinkEntry.php +++ b/src/Filament/Integration/Components/Infolists/LinkEntry.php @@ -5,12 +5,13 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Infolists; use Filament\Infolists\Components\ViewEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Models\CustomField; final class LinkEntry extends AbstractInfolistEntry { - public function make(CustomField $customField): ViewEntry + public function make(CustomField $customField, ?Model $record = null): ViewEntry { return ViewEntry::make($customField->getFieldName()) ->label($customField->name) diff --git a/src/Filament/Integration/Components/Infolists/MultiChoiceEntry.php b/src/Filament/Integration/Components/Infolists/MultiChoiceEntry.php index 029b3237..932f8bc1 100644 --- a/src/Filament/Integration/Components/Infolists/MultiChoiceEntry.php +++ b/src/Filament/Integration/Components/Infolists/MultiChoiceEntry.php @@ -7,12 +7,14 @@ use Filament\Infolists\Components\Entry; use Filament\Infolists\Components\TextEntry as BaseTextEntry; use Filament\Infolists\Components\ViewEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Filament\Integration\Concerns\Shared\ConfiguresBadgeColors; use Relaticle\CustomFields\Models\Contracts\HasCustomFields; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldOption; use Relaticle\CustomFields\Services\ValueResolver\LookupMultiValueResolver; final class MultiChoiceEntry extends AbstractInfolistEntry @@ -23,7 +25,7 @@ public function __construct( private readonly LookupMultiValueResolver $valueResolver, ) {} - public function make(CustomField $customField): Entry + public function make(CustomField $customField, ?Model $record = null): Entry { if ($customField->typeData->acceptsArbitraryValues) { return $this->makeTagsEntry($customField); @@ -57,12 +59,12 @@ private function resolveOptionColors(CustomField $customField): array { if (! FeatureManager::isEnabled(CustomFieldsFeature::FIELD_OPTION_COLORS) || ! $customField->settings->enable_option_colors - || $customField->lookup_type) { + || $customField->typeData->requiresRelationship) { return []; } return $customField->options - ->filter(fn ($option): bool => filled($option->settings->color)) + ->filter(fn (CustomFieldOption $option): bool => filled($option->settings->color)) ->pluck('settings.color', 'id') ->all(); } diff --git a/src/Filament/Integration/Components/Infolists/PhoneEntry.php b/src/Filament/Integration/Components/Infolists/PhoneEntry.php index ccbdc202..7a32e1d1 100644 --- a/src/Filament/Integration/Components/Infolists/PhoneEntry.php +++ b/src/Filament/Integration/Components/Infolists/PhoneEntry.php @@ -5,12 +5,13 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Infolists; use Filament\Infolists\Components\ViewEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Models\CustomField; final class PhoneEntry extends AbstractInfolistEntry { - public function make(CustomField $customField): ViewEntry + public function make(CustomField $customField, ?Model $record = null): ViewEntry { return ViewEntry::make($customField->getFieldName()) ->label($customField->name) diff --git a/src/Filament/Integration/Components/Infolists/RecordEntry.php b/src/Filament/Integration/Components/Infolists/RecordEntry.php index 80bc9db4..ef5aad9a 100644 --- a/src/Filament/Integration/Components/Infolists/RecordEntry.php +++ b/src/Filament/Integration/Components/Infolists/RecordEntry.php @@ -6,116 +6,71 @@ use Filament\Infolists\Components\ViewEntry; use Illuminate\Database\Eloquent\Model; -use InvalidArgumentException; -use Relaticle\CustomFields\Data\AvatarConfiguration; +use Relaticle\CustomFields\Enums\UiSurface; use Relaticle\CustomFields\Facades\Entities; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; +use Relaticle\CustomFields\Filament\Integration\Support\RecordChips; use Relaticle\CustomFields\Models\Contracts\HasCustomFields; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldRelationship; +use Relaticle\CustomFields\Services\Relationships\MissingRelationshipDefinitions; +use Relaticle\CustomFields\Support\ViewFlavor; final class RecordEntry extends AbstractInfolistEntry { - public function make(CustomField $customField): ViewEntry + public function make(CustomField $customField, ?Model $record = null): ViewEntry { - if ($customField->lookup_type === null) { + $definition = $customField->relationshipDefinition(); + + // The entry has nothing to read without a definition, so it leaves the infolist alone. + if (! $definition instanceof CustomFieldRelationship) { + app(MissingRelationshipDefinitions::class)->report($customField); + return ViewEntry::make($customField->getFieldName()) ->label($customField->name) ->view('custom-fields::infolists.record-entry') - ->state(['records' => [], 'multiple' => false]); + ->state(['records' => [], 'multiple' => false, 'chipsView' => null]) + ->hidden(); } - $entity = Entities::getEntity($customField->lookup_type); - $isMultiSelect = $customField->settings->allow_multiple ?? false; + $entity = Entities::getEntity($definition->targetEntityTypeFor($customField)); + $isMultiSelect = $customField->allowsMultipleRecords(); return ViewEntry::make($customField->getFieldName()) ->label($customField->name) ->view('custom-fields::infolists.record-entry') ->state(function (HasCustomFields $record) use ($customField, $entity, $isMultiSelect): array { $value = $record->getCustomFieldValue($customField); + $recordIds = match (true) { + $value === null => [], + is_array($value) => $value, + default => [$value], + }; - if ($value === null || (is_array($value) && $value === [])) { - return ['records' => [], 'multiple' => $isMultiSelect]; - } - - if ($entity === null) { - return ['records' => [], 'multiple' => $isMultiSelect]; - } - - $avatarConfig = $entity->getAvatarConfiguration(); - $titleAttribute = $entity->getPrimaryAttribute(); - - $recordIds = is_array($value) ? $value : [$value]; - $records = $entity->newQuery()->whereIn('id', $recordIds)->get() - ->sortBy(fn (Model $record): int|false => array_search($record->getKey(), $recordIds, true)); - - $formattedRecords = $records->map(function (Model $relatedRecord) use ($avatarConfig, $titleAttribute, $entity): array { - return $this->formatRecord($relatedRecord, $avatarConfig, $titleAttribute, $entity); - })->toArray(); + $chips = app(RecordChips::class); return [ - 'records' => $formattedRecords, + 'records' => $chips->build($entity, $recordIds, $this->provenance($chips, $record, $customField)), 'multiple' => $isMultiSelect, + 'chipsView' => $customField->supportsPairing() ? ViewFlavor::view(UiSurface::RecordChips) : null, ]; }); } - private function formatRecord( - Model $record, - ?AvatarConfiguration $avatarConfig, - string $titleAttribute, - mixed $entity, - ): array { - $name = $record->getAttribute($titleAttribute) ?? ''; - $avatarUrl = $this->getAvatarUrl($record, $avatarConfig); - $shapeClass = $avatarConfig?->getCssClass() ?? 'rounded-full'; - $url = $this->getRecordUrl($record, $entity); - - return [ - 'name' => $name, - 'avatarUrl' => $avatarUrl, - 'avatarShape' => $shapeClass, - 'url' => $url, - ]; - } - - private function getAvatarUrl(Model $record, ?AvatarConfiguration $avatarConfig): ?string + /** + * One record's page can afford the actor morph, which a table page cannot, so this is the + * one surface that says who made the link rather than only when it was made. + * + * @return array + */ + private function provenance(RecordChips $chips, HasCustomFields $record, CustomField $customField): array { - if (! $avatarConfig instanceof AvatarConfiguration || ! $avatarConfig->hasAttribute()) { - return null; + if (! $customField->supportsPairing() || ! $record instanceof Model) { + return []; } - return $record->getAttribute($avatarConfig->attribute); - } - - private function getRecordUrl(Model $record, mixed $entity): ?string - { - $recordPage = $entity->getRecordPage(); - - if ($recordPage === null) { - return null; - } - - $resourceClass = $entity->getResourceClass(); - - if ($resourceClass === null || ! class_exists($resourceClass)) { - return null; - } - - if (! method_exists($resourceClass, 'getUrl')) { - return null; - } - - if (! array_key_exists($recordPage, $resourceClass::getPages())) { - throw new InvalidArgumentException(sprintf( - "Entity '%s' has recordPage '%s' but %s does not define a '%s' page. Available pages: %s.", - $entity->getLabelSingular(), - $recordPage, - class_basename($resourceClass), - $recordPage, - implode(', ', array_keys($resourceClass::getPages())), - )); - } + $record->loadMissing(['outgoingLinks.createdBy', 'incomingLinks.createdBy']); - return $resourceClass::getUrl($recordPage, ['record' => $record]); + return $chips->provenance($record, $customField); } } diff --git a/src/Filament/Integration/Components/Infolists/SingleChoiceEntry.php b/src/Filament/Integration/Components/Infolists/SingleChoiceEntry.php index b527ef7a..36202ffb 100644 --- a/src/Filament/Integration/Components/Infolists/SingleChoiceEntry.php +++ b/src/Filament/Integration/Components/Infolists/SingleChoiceEntry.php @@ -6,6 +6,7 @@ use Filament\Infolists\Components\Entry; use Filament\Infolists\Components\TextEntry as BaseTextEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Filament\Integration\Concerns\Shared\ConfiguresBadgeColors; use Relaticle\CustomFields\Models\CustomField; @@ -19,7 +20,7 @@ public function __construct( private readonly LookupSingleValueResolver $valueResolver ) {} - public function make(CustomField $customField): Entry + public function make(CustomField $customField, ?Model $record = null): Entry { $entry = BaseTextEntry::make($customField->getFieldName()) ->label($customField->name); diff --git a/src/Filament/Integration/Components/Infolists/TextEntry.php b/src/Filament/Integration/Components/Infolists/TextEntry.php index 09b8f5f5..e2c46032 100644 --- a/src/Filament/Integration/Components/Infolists/TextEntry.php +++ b/src/Filament/Integration/Components/Infolists/TextEntry.php @@ -5,12 +5,13 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Infolists; use Filament\Infolists\Components\TextEntry as BaseTextEntry; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractInfolistEntry; use Relaticle\CustomFields\Models\CustomField; final class TextEntry extends AbstractInfolistEntry { - public function make(CustomField $customField): BaseTextEntry + public function make(CustomField $customField, ?Model $record = null): BaseTextEntry { return BaseTextEntry::make($customField->getFieldName()) ->label($customField->name) diff --git a/src/Filament/Integration/Components/Tables/Columns/ColorColumn.php b/src/Filament/Integration/Components/Tables/Columns/ColorColumn.php index a1edf9d2..49b061be 100644 --- a/src/Filament/Integration/Components/Tables/Columns/ColorColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/ColorColumn.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns; use Filament\Tables\Columns\ColorColumn as BaseColorColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnState; @@ -17,7 +18,7 @@ final class ColorColumn extends AbstractTableColumn use ConfiguresColumnState; use ConfiguresSearchable; - public function make(CustomField $customField): BaseColorColumn + public function make(CustomField $customField, ?Model $record = null): BaseColorColumn { $column = BaseColorColumn::make($customField->getFieldName()); diff --git a/src/Filament/Integration/Components/Tables/Columns/CurrencyColumn.php b/src/Filament/Integration/Components/Tables/Columns/CurrencyColumn.php index 64890de8..1b4ce364 100644 --- a/src/Filament/Integration/Components/Tables/Columns/CurrencyColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/CurrencyColumn.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns; use Filament\Tables\Columns\TextColumn as BaseTextColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Shared\ConfiguresCurrencyFormatting; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; @@ -21,7 +22,7 @@ final class CurrencyColumn extends AbstractTableColumn use ConfiguresSearchable; use ConfiguresSortable; - public function make(CustomField $customField): BaseTextColumn + public function make(CustomField $customField, ?Model $record = null): BaseTextColumn { $column = BaseTextColumn::make($customField->getFieldName()); diff --git a/src/Filament/Integration/Components/Tables/Columns/DateTimeColumn.php b/src/Filament/Integration/Components/Tables/Columns/DateTimeColumn.php index 6a5a8516..dee2fa6a 100644 --- a/src/Filament/Integration/Components/Tables/Columns/DateTimeColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/DateTimeColumn.php @@ -7,6 +7,7 @@ use Closure; use Filament\Tables\Columns\Column as BaseColumn; use Filament\Tables\Columns\TextColumn as BaseTextColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\CustomFields; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; @@ -22,7 +23,7 @@ class DateTimeColumn extends AbstractTableColumn protected ?Closure $locale = null; - public function make(CustomField $customField): BaseColumn + public function make(CustomField $customField, ?Model $record = null): BaseColumn { $column = BaseTextColumn::make($customField->getFieldName()); diff --git a/src/Filament/Integration/Components/Tables/Columns/EmailColumn.php b/src/Filament/Integration/Components/Tables/Columns/EmailColumn.php index 9291f478..10529f4c 100644 --- a/src/Filament/Integration/Components/Tables/Columns/EmailColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/EmailColumn.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns; use Filament\Tables\Columns\TextColumn as BaseTextColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresSearchable; @@ -16,7 +17,7 @@ final class EmailColumn extends AbstractTableColumn use ConfiguresColumnLabel; use ConfiguresSearchable; - public function make(CustomField $customField): BaseTextColumn + public function make(CustomField $customField, ?Model $record = null): BaseTextColumn { $column = BaseTextColumn::make($customField->getFieldName()) ->view('custom-fields::tables.columns.email-column'); diff --git a/src/Filament/Integration/Components/Tables/Columns/IconColumn.php b/src/Filament/Integration/Components/Tables/Columns/IconColumn.php index 5b55cec1..61c4ab2a 100644 --- a/src/Filament/Integration/Components/Tables/Columns/IconColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/IconColumn.php @@ -6,6 +6,7 @@ use Filament\Tables\Columns\Column; use Filament\Tables\Columns\IconColumn as BaseIconColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresSortable; @@ -17,7 +18,7 @@ class IconColumn extends AbstractTableColumn use ConfiguresColumnLabel; use ConfiguresSortable; - public function make(CustomField $customField): Column + public function make(CustomField $customField, ?Model $record = null): Column { $column = BaseIconColumn::make($customField->getFieldName())->boolean(); diff --git a/src/Filament/Integration/Components/Tables/Columns/LinkColumn.php b/src/Filament/Integration/Components/Tables/Columns/LinkColumn.php index 11c82090..0c2d1ae9 100644 --- a/src/Filament/Integration/Components/Tables/Columns/LinkColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/LinkColumn.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns; use Filament\Tables\Columns\TextColumn as BaseTextColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresSearchable; @@ -16,7 +17,7 @@ final class LinkColumn extends AbstractTableColumn use ConfiguresColumnLabel; use ConfiguresSearchable; - public function make(CustomField $customField): BaseTextColumn + public function make(CustomField $customField, ?Model $record = null): BaseTextColumn { $column = BaseTextColumn::make($customField->getFieldName()) ->view('custom-fields::tables.columns.link-column'); diff --git a/src/Filament/Integration/Components/Tables/Columns/MultiChoiceColumn.php b/src/Filament/Integration/Components/Tables/Columns/MultiChoiceColumn.php index 6947a2e7..4dfc200b 100644 --- a/src/Filament/Integration/Components/Tables/Columns/MultiChoiceColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/MultiChoiceColumn.php @@ -6,6 +6,7 @@ use Filament\Tables\Columns\Column as BaseColumn; use Filament\Tables\Columns\TextColumn as BaseTextColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Shared\ConfiguresBadgeColors; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; @@ -20,7 +21,7 @@ final class MultiChoiceColumn extends AbstractTableColumn public function __construct(public LookupMultiValueResolver $valueResolver) {} - public function make(CustomField $customField): BaseColumn + public function make(CustomField $customField, ?Model $record = null): BaseColumn { $column = BaseTextColumn::make($customField->getFieldName()); diff --git a/src/Filament/Integration/Components/Tables/Columns/PhoneColumn.php b/src/Filament/Integration/Components/Tables/Columns/PhoneColumn.php index ef5a3f82..750cb117 100644 --- a/src/Filament/Integration/Components/Tables/Columns/PhoneColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/PhoneColumn.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns; use Filament\Tables\Columns\TextColumn as BaseTextColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresSearchable; @@ -21,7 +22,7 @@ public function __construct( private readonly CountryPhoneService $phoneService, ) {} - public function make(CustomField $customField): BaseTextColumn + public function make(CustomField $customField, ?Model $record = null): BaseTextColumn { $column = BaseTextColumn::make($customField->getFieldName()) ->view('custom-fields::tables.columns.phone-column'); diff --git a/src/Filament/Integration/Components/Tables/Columns/RecordColumn.php b/src/Filament/Integration/Components/Tables/Columns/RecordColumn.php index a7f6c8fa..9378499b 100644 --- a/src/Filament/Integration/Components/Tables/Columns/RecordColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/RecordColumn.php @@ -4,21 +4,21 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns; -use Filament\Tables\Columns\Column; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use InvalidArgumentException; -use Relaticle\CustomFields\Data\AvatarConfiguration; use Relaticle\CustomFields\Facades\Entities; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; -use Relaticle\CustomFields\Models\Contracts\HasCustomFields; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldRelationship; +use Relaticle\CustomFields\QueryBuilders\RecordLinkQuery; +use Relaticle\CustomFields\Services\Relationships\MissingRelationshipDefinitions; final class RecordColumn extends AbstractTableColumn { use ConfiguresColumnLabel; - public function make(CustomField $customField): RecordColumnView + public function make(CustomField $customField, ?Model $record = null): RecordColumnView { $column = RecordColumnView::make($customField->getFieldName()) ->customField($customField) @@ -30,131 +30,80 @@ public function make(CustomField $customField): RecordColumnView $this->configureLabel($column, $customField); - return $column; - } -} - -/** - * Custom Filament column that renders records using a blade view. - */ -final class RecordColumnView extends Column -{ - protected string $view = 'custom-fields::tables.columns.record-column'; + $definition = $customField->relationshipDefinition(); - private ?CustomField $customField = null; + // A column that cannot say where the field points takes itself out of the table + // rather than the table out of the page. + if (! $definition instanceof CustomFieldRelationship) { + app(MissingRelationshipDefinitions::class)->report($customField); - private bool $multiple = false; - - private mixed $entity = null; - - private ?AvatarConfiguration $avatarConfig = null; - - private ?string $titleAttribute = null; - - public function customField(CustomField $customField): static - { - $this->customField = $customField; - - if ($customField->lookup_type !== null) { - $this->entity = Entities::getEntity($customField->lookup_type); - $this->multiple = $customField->settings->allow_multiple ?? false; - - if ($this->entity !== null) { - $this->avatarConfig = $this->entity->getAvatarConfiguration(); - $this->titleAttribute = $this->entity->getPrimaryAttribute(); - } + return $column->hidden(); } - return $this; - } + $this->configureSorting($column, $customField, $definition); + $this->configureSearching($column, $customField, $definition); - public function isMultiple(): bool - { - return $this->multiple; + return $column; } - public function getRecords(Model $record): array + /** + * Sorting joins the target's primary attribute, so an entity the host has not registered + * leaves the column unsorted rather than ordering by nothing. + */ + private function configureSorting(RecordColumnView $column, CustomField $customField, CustomFieldRelationship $definition): void { - if (! $record instanceof HasCustomFields || ! $this->customField instanceof CustomField) { - return []; - } - - $value = $record->getCustomFieldValue($this->customField); - - if ($value === null || (is_array($value) && $value === [])) { - return []; - } - - if ($this->entity === null) { - return []; - } - - $recordIds = is_array($value) ? $value : [$value]; - $records = $this->entity->newQuery()->whereIn('id', $recordIds)->get() - ->sortBy(fn (Model $record): int|false => array_search($record->getKey(), $recordIds, true)); - - return $records->map(function (Model $relatedRecord): array { - return $this->formatRecord($relatedRecord); - })->toArray(); + $attribute = $this->primaryAttribute($definition->targetEntityTypeFor($customField)); + + $column->sortable( + condition: $attribute !== null, + query: function (Builder $query, string $direction) use ($customField, $definition, $attribute): Builder { + if ($attribute === null) { + return $query; + } + + return app(RecordLinkQuery::class)->orderByLinkedAttribute( + $query, + $definition, + $definition->readDirectionFor($customField), + $attribute, + $direction, + ); + }, + ); } - private function formatRecord(Model $record): array + private function configureSearching(RecordColumnView $column, CustomField $customField, CustomFieldRelationship $definition): void { - $name = $record->getAttribute($this->titleAttribute) ?? ''; - $avatarUrl = $this->getAvatarUrl($record); - $shapeClass = $this->avatarConfig?->getCssClass() ?? 'rounded-full'; - $url = $this->getRecordUrl($record); - - return [ - 'name' => $name, - 'avatarUrl' => $avatarUrl, - 'avatarShape' => $shapeClass, - 'url' => $url, - ]; + $column->searchable( + condition: $customField->settings->searchable, + query: fn (Builder $query, string $search): Builder => app(RecordLinkQuery::class)->whereLinkedMatching( + $query, + $definition, + $definition->readDirectionFor($customField), + $this->searchAttributes($definition->targetEntityTypeFor($customField)), + $search, + ), + ); } - private function getAvatarUrl(Model $record): ?string + private function primaryAttribute(string $entityType): ?string { - if (! $this->avatarConfig instanceof AvatarConfiguration || ! $this->avatarConfig->hasAttribute()) { - return null; - } - - return $record->getAttribute($this->avatarConfig->attribute); + return Entities::getEntity($entityType)?->getPrimaryAttribute(); } - private function getRecordUrl(Model $record): ?string + /** + * @return array + */ + private function searchAttributes(string $entityType): array { - if ($this->entity === null) { - return null; - } - - $recordPage = $this->entity->getRecordPage(); - - if ($recordPage === null) { - return null; - } - - $resourceClass = $this->entity->getResourceClass(); + $entity = Entities::getEntity($entityType); - if ($resourceClass === null || ! class_exists($resourceClass)) { - return null; - } - - if (! method_exists($resourceClass, 'getUrl')) { - return null; + if ($entity === null) { + return []; } - if (! array_key_exists($recordPage, $resourceClass::getPages())) { - throw new InvalidArgumentException(sprintf( - "Entity '%s' has recordPage '%s' but %s does not define a '%s' page. Available pages: %s.", - $this->entity->getLabelSingular(), - $recordPage, - class_basename($resourceClass), - $recordPage, - implode(', ', array_keys($resourceClass::getPages())), - )); - } + $attributes = $entity->getSearchAttributes(); - return $resourceClass::getUrl($recordPage, ['record' => $record]); + return $attributes === [] ? [$entity->getPrimaryAttribute()] : $attributes; } } diff --git a/src/Filament/Integration/Components/Tables/Columns/RecordColumnView.php b/src/Filament/Integration/Components/Tables/Columns/RecordColumnView.php new file mode 100644 index 00000000..31bb9b64 --- /dev/null +++ b/src/Filament/Integration/Components/Tables/Columns/RecordColumnView.php @@ -0,0 +1,144 @@ +through = $relation; + + return $this; + } + + /** + * A cell-level gate. Filament evaluates a column's own visibility once per table, so a + * per-record condition has to be answered where the cell is built. + * + * @param (Closure(Model): bool)|null $callback + */ + public function renderFor(?Closure $callback): static + { + $this->shouldRenderFor = $callback; + + return $this; + } + + public function customField(CustomField $customField): static + { + $this->customField = $customField; + $entityType = $customField->targetEntityType(); + + if ($entityType !== null) { + $this->entity = Entities::getEntity($entityType); + $this->multiple = $customField->allowsMultipleRecords(); + } + + return $this; + } + + public function isMultiple(): bool + { + return $this->multiple; + } + + /** + * Chips are what a paired relationship draws. A record column keeps the row of linked + * names it has always drawn, in either flavor. + */ + public function getChipsView(): ?string + { + return $this->customField?->supportsPairing() === true + ? ViewFlavor::view(UiSurface::RecordChips) + : null; + } + + /** + * @return array + */ + public function getRecords(Model $record): array + { + // The path is validated before the gate, so an unsupported one is reported whether or + // not the cell would have rendered. + $subject = $this->through === null + ? $record + : app(ThroughRelationResolver::class)->relatedRecord($record, $this->through); + + if ($this->shouldRenderFor instanceof Closure && ! ($this->shouldRenderFor)($record)) { + return []; + } + + if (! $subject instanceof HasCustomFields || ! $this->customField instanceof CustomField) { + return []; + } + + $value = $subject->getCustomFieldValue($this->customField); + $recordIds = match (true) { + $value === null => [], + is_array($value) => $value, + default => [$value], + }; + + $chips = app(RecordChips::class); + + return $chips->build($this->entity, $recordIds, $this->provenance($chips, $subject)); + } + + /** + * A table page reads provenance from the edges it already loaded. Loading the actor here + * would be a query per row, so the host eager loads outgoingLinks.createdBy and + * incomingLinks.createdBy, or the chip says nothing about where the link came from. + * + * Both link relations have to be loaded, not either: the reader falls back to SQL as soon + * as one relation it needs for the direction is missing, which is the per-row query this + * guard exists to prevent. + * + * @return array + */ + private function provenance(RecordChips $chips, HasCustomFields $subject): array + { + if (! $subject instanceof Model || ! $this->customField instanceof CustomField) { + return []; + } + + if (! $subject->relationLoaded('outgoingLinks') || ! $subject->relationLoaded('incomingLinks')) { + return []; + } + + return $chips->provenance($subject, $this->customField); + } +} diff --git a/src/Filament/Integration/Components/Tables/Columns/RichTextColumn.php b/src/Filament/Integration/Components/Tables/Columns/RichTextColumn.php index a96e9f72..e915876c 100644 --- a/src/Filament/Integration/Components/Tables/Columns/RichTextColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/RichTextColumn.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns; use Filament\Tables\Columns\TextColumn as BaseTextColumn; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; @@ -25,7 +26,7 @@ final class RichTextColumn extends AbstractTableColumn private const int DEFAULT_TOOLTIP_LIMIT = 500; - public function make(CustomField $customField): BaseTextColumn + public function make(CustomField $customField, ?Model $record = null): BaseTextColumn { $column = BaseTextColumn::make($customField->getFieldName()); diff --git a/src/Filament/Integration/Components/Tables/Columns/SingleChoiceColumn.php b/src/Filament/Integration/Components/Tables/Columns/SingleChoiceColumn.php index 3201f631..f30ca115 100644 --- a/src/Filament/Integration/Components/Tables/Columns/SingleChoiceColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/SingleChoiceColumn.php @@ -6,6 +6,7 @@ use Filament\Tables\Columns\Column as BaseColumn; use Filament\Tables\Columns\TextColumn as BaseTextColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Shared\ConfiguresBadgeColors; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; @@ -22,7 +23,7 @@ final class SingleChoiceColumn extends AbstractTableColumn public function __construct(public LookupSingleValueResolver $valueResolver) {} - public function make(CustomField $customField): BaseColumn + public function make(CustomField $customField, ?Model $record = null): BaseColumn { $column = BaseTextColumn::make($customField->getFieldName()); diff --git a/src/Filament/Integration/Components/Tables/Columns/TextColumn.php b/src/Filament/Integration/Components/Tables/Columns/TextColumn.php index f2c730a2..8a99e743 100644 --- a/src/Filament/Integration/Components/Tables/Columns/TextColumn.php +++ b/src/Filament/Integration/Components/Tables/Columns/TextColumn.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Components\Tables\Columns; use Filament\Tables\Columns\TextColumn as BaseTextColumn; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableColumn; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnLabel; use Relaticle\CustomFields\Filament\Integration\Concerns\Tables\ConfiguresColumnState; @@ -19,7 +20,7 @@ final class TextColumn extends AbstractTableColumn use ConfiguresSearchable; use ConfiguresSortable; - public function make(CustomField $customField): BaseTextColumn + public function make(CustomField $customField, ?Model $record = null): BaseTextColumn { $column = BaseTextColumn::make($customField->getFieldName()); diff --git a/src/Filament/Integration/Components/Tables/Filters/RecordFilter.php b/src/Filament/Integration/Components/Tables/Filters/RecordFilter.php index 74071f7e..b6bde74f 100644 --- a/src/Filament/Integration/Components/Tables/Filters/RecordFilter.php +++ b/src/Filament/Integration/Components/Tables/Filters/RecordFilter.php @@ -8,13 +8,15 @@ use Filament\Tables\Filters\SelectFilter as FilamentSelectFilter; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\App; use InvalidArgumentException; use Relaticle\CustomFields\Data\AvatarConfiguration; use Relaticle\CustomFields\Facades\Entities; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableFilter; use Relaticle\CustomFields\Models\CustomField; -use Relaticle\CustomFields\Support\Utils; +use Relaticle\CustomFields\Models\CustomFieldRelationship; +use Relaticle\CustomFields\QueryBuilders\EntitySearchQuery; +use Relaticle\CustomFields\QueryBuilders\RecordLinkQuery; +use Relaticle\CustomFields\Services\Relationships\MissingRelationshipDefinitions; use Throwable; final class RecordFilter extends AbstractTableFilter @@ -22,7 +24,7 @@ final class RecordFilter extends AbstractTableFilter /** * @throws Throwable */ - public function make(CustomField $customField): FilamentSelectFilter + public function make(CustomField $customField, ?Model $record = null, ?string $through = null): FilamentSelectFilter { $filter = FilamentSelectFilter::make($customField->getFieldName()) ->multiple() @@ -31,23 +33,26 @@ public function make(CustomField $customField): FilamentSelectFilter ->native(false) ->modifyFormFieldUsing(fn (Select $field): Select => $field->allowHtml()); - $filter = $this->configureLookup($filter, $customField->lookup_type); + $definition = $customField->relationshipDefinition(); - $filter->query(function (array $data, Builder $query) use ($customField): Builder { - if (empty($data['values'])) { - return $query; - } + // Nothing to filter by while the field points nowhere, and a filter that throws here + // would take the whole table with it. + if (! $definition instanceof CustomFieldRelationship) { + app(MissingRelationshipDefinitions::class)->report($customField); - return $query->whereHas('customFieldValues', function (Builder $q) use ($customField, $data): void { - $q->where('custom_field_id', $customField->id); + return $filter->hidden(); + } - $q->where(function (Builder $subQuery) use ($data): void { - foreach ($data['values'] as $value) { - $subQuery->orWhereJsonContains('json_value', $value); - } - }); - }); - }); + $filter = $this->configureLookup($filter, $definition->targetEntityTypeFor($customField)); + + $filter->query(fn (array $data, Builder $query): Builder => empty($data['values']) + ? $query + : $this->constrainThrough($query, $through, fn (Builder $query): Builder => app(RecordLinkQuery::class)->whereLinkedTo( + $query, + $definition, + $definition->readDirectionFor($customField), + $data['values'], + ))); return $filter; } @@ -55,12 +60,8 @@ public function make(CustomField $customField): FilamentSelectFilter /** * @throws Throwable */ - private function configureLookup(FilamentSelectFilter $filter, ?string $lookupType): FilamentSelectFilter + private function configureLookup(FilamentSelectFilter $filter, string $lookupType): FilamentSelectFilter { - if ($lookupType === null) { - return $filter; - } - $entity = Entities::getEntity($lookupType); if ($entity === null) { @@ -69,36 +70,17 @@ private function configureLookup(FilamentSelectFilter $filter, ?string $lookupTy $entityInstance = $entity->createModelInstance(); $recordTitleAttribute = $entity->getPrimaryAttribute(); - $globalSearchableAttributes = $entity->getSearchAttributes(); + $searchAttributes = $entity->getSearchAttributes(); $avatarConfig = $entity->getAvatarConfiguration(); - $resource = null; - - if ($entity->getResourceClass()) { - try { - $resource = App::make($entity->getResourceClass()); - } catch (Throwable) { - $resource = null; - } + $resourceClass = $entity->getResourceClass(); + + if ($searchAttributes === []) { + $searchAttributes = [$recordTitleAttribute]; } return $filter - ->getSearchResultsUsing(function (string $search) use ($entityInstance, $recordTitleAttribute, $globalSearchableAttributes, $resource, $avatarConfig): array { - $query = $entityInstance->query(); - - if ($resource !== null) { - Utils::invokeMethodByReflection($resource, 'applyGlobalSearchAttributeConstraints', [ - $query, - $search, - $globalSearchableAttributes, - ]); - } else { - $query->where(function (Builder $q) use ($search, $globalSearchableAttributes, $recordTitleAttribute): void { - $searchAttributes = $globalSearchableAttributes === [] ? [$recordTitleAttribute] : $globalSearchableAttributes; - foreach ($searchAttributes as $attribute) { - $q->orWhere($attribute, 'like', sprintf('%%%s%%', $search)); - } - }); - } + ->getSearchResultsUsing(function (string $search) use ($entityInstance, $recordTitleAttribute, $searchAttributes, $avatarConfig, $resourceClass): array { + $query = app(EntitySearchQuery::class)->apply($entityInstance->query(), $search, $searchAttributes, $resourceClass); $records = $query->limit(50)->get(); @@ -114,7 +96,7 @@ private function configureLookup(FilamentSelectFilter $filter, ?string $lookupTy }) ->getOptionLabelsUsing(function (array $values) use ($entityInstance, $recordTitleAttribute, $avatarConfig): array { $records = $entityInstance::query() - ->whereIn('id', $values) + ->whereKey($values) ->get(); return $this->formatOptionsWithAvatars($records, $recordTitleAttribute, $avatarConfig); @@ -122,6 +104,7 @@ private function configureLookup(FilamentSelectFilter $filter, ?string $lookupTy } /** + * @param iterable $records * @return array */ private function formatOptionsWithAvatars( diff --git a/src/Filament/Integration/Components/Tables/Filters/SelectFilter.php b/src/Filament/Integration/Components/Tables/Filters/SelectFilter.php index e5f71ba0..9935c9f2 100644 --- a/src/Filament/Integration/Components/Tables/Filters/SelectFilter.php +++ b/src/Filament/Integration/Components/Tables/Filters/SelectFilter.php @@ -8,12 +8,13 @@ use Filament\Tables\Filters\Indicator; use Filament\Tables\Filters\SelectFilter as FilamentSelectFilter; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableFilter; use Relaticle\CustomFields\Models\CustomField; final class SelectFilter extends AbstractTableFilter { - public function make(CustomField $customField): FilamentSelectFilter + public function make(CustomField $customField, ?Model $record = null, ?string $through = null): FilamentSelectFilter { $filter = FilamentSelectFilter::make($customField->getFieldName()) ->multiple() @@ -25,11 +26,11 @@ public function make(CustomField $customField): FilamentSelectFilter $filter->query( fn (array $data, Builder $query): Builder => $query->when( ! empty($data['values']), - fn (Builder $query): Builder => $query->whereHas('customFieldValues', function (Builder $query) use ($customField, $data): void { + fn (Builder $query): Builder => $this->constrainThrough($query, $through, fn (Builder $query): Builder => $query->whereHas('customFieldValues', function (Builder $query) use ($customField, $data): void { $query->where('custom_field_id', $customField->id) ->when($customField->getValueColumn() === 'json_value', fn (Builder $query) => $query->whereJsonContains($customField->getValueColumn(), $data['values'])) ->when($customField->getValueColumn() !== 'json_value', fn (Builder $query) => $query->whereIn($customField->getValueColumn(), $data['values'])); - }), + })), ) ); diff --git a/src/Filament/Integration/Components/Tables/Filters/TagsFilter.php b/src/Filament/Integration/Components/Tables/Filters/TagsFilter.php index 5697871f..5b80d027 100644 --- a/src/Filament/Integration/Components/Tables/Filters/TagsFilter.php +++ b/src/Filament/Integration/Components/Tables/Filters/TagsFilter.php @@ -8,13 +8,14 @@ use Filament\Tables\Filters\Indicator; use Filament\Tables\Filters\SelectFilter as FilamentSelectFilter; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\CustomFields; use Relaticle\CustomFields\Filament\Integration\Base\AbstractTableFilter; use Relaticle\CustomFields\Models\CustomField; final class TagsFilter extends AbstractTableFilter { - public function make(CustomField $customField): FilamentSelectFilter + public function make(CustomField $customField, ?Model $record = null, ?string $through = null): FilamentSelectFilter { $filter = FilamentSelectFilter::make($customField->getFieldName()) ->multiple() @@ -26,13 +27,13 @@ public function make(CustomField $customField): FilamentSelectFilter $filter->query( fn (array $data, Builder $query): Builder => $query->when( ! empty($data['values']), - fn (Builder $query): Builder => $query->whereHas('customFieldValues', function (Builder $query) use ($customField, $data): void { + fn (Builder $query): Builder => $this->constrainThrough($query, $through, fn (Builder $query): Builder => $query->whereHas('customFieldValues', function (Builder $query) use ($customField, $data): void { $query->where('custom_field_id', $customField->id); foreach ($data['values'] as $tag) { $query->whereJsonContains('json_value', $tag); } - }), + })), ) ); diff --git a/src/Filament/Integration/Components/Tables/Filters/TernaryFilter.php b/src/Filament/Integration/Components/Tables/Filters/TernaryFilter.php index b8c52dff..269a8818 100644 --- a/src/Filament/Integration/Components/Tables/Filters/TernaryFilter.php +++ b/src/Filament/Integration/Components/Tables/Filters/TernaryFilter.php @@ -1,15 +1,18 @@ getFieldName()) ->label($customField->name) @@ -19,18 +22,18 @@ public function make(CustomField $customField): FilamentTernaryFilter ]) ->nullable() ->queries( - true: fn (Builder $query) => $query + true: fn (Builder $query): Builder => $this->constrainThrough($query, $through, fn (Builder $query): Builder => $query ->whereHas('customFieldValues', function (Builder $query) use ($customField): void { $query->where('custom_field_id', $customField->getKey())->where($customField->getValueColumn(), true); - }), - false: fn (Builder $query) => $query + })), + false: fn (Builder $query): Builder => $this->constrainThrough($query, $through, fn (Builder $query): Builder => $query ->where(fn (Builder $query) => $query ->whereHas('customFieldValues', function (Builder $query) use ($customField): void { $query->where('custom_field_id', $customField->getKey())->where($customField->getValueColumn(), false); })->orWhereDoesntHave('customFieldValues', function (Builder $query) use ($customField): void { $query->where('custom_field_id', $customField->getKey())->where($customField->getValueColumn(), true); }) - ) + )) ); } } diff --git a/src/Filament/Integration/Concerns/Forms/ConfiguresRecordSelects.php b/src/Filament/Integration/Concerns/Forms/ConfiguresRecordSelects.php new file mode 100644 index 00000000..7b0826f5 --- /dev/null +++ b/src/Filament/Integration/Concerns/Forms/ConfiguresRecordSelects.php @@ -0,0 +1,65 @@ +relationshipDefinition(); + $allowMultiple = $customField->allowsMultipleRecords(); + $maxValues = $allowMultiple ? self::MAX_MULTIPLE_RECORDS : 1; + + $component + ->customField($customField) + ->lookupType($customField->targetEntityType()) + ->allowMultiple($allowMultiple) + ->maxValues($maxValues) + ->placeholder(__('custom-fields::custom-fields.record.search_placeholder')) + ->addLabel(__('custom-fields::custom-fields.record.add_record_placeholder')) + ->rules($this->recordValueRules($definition, $maxValues)); + + // A hidden field is never dehydrated, so a form that cannot show the field cannot + // write it either, which is what a missing definition should mean on a write path. + if (! $definition instanceof CustomFieldRelationship) { + app(MissingRelationshipDefinitions::class)->report($customField); + + $component->hidden(); + } + + return $component; + } + + /** + * Cardinality caps a relationship slot, and says so in words the user can act on, so a + * count rule beside it would report one mistake twice. A field with no definition still + * needs one. + * + * @return array + */ + private function recordValueRules(?CustomFieldRelationship $definition, int $maxValues): array + { + return $definition instanceof CustomFieldRelationship + ? ['array'] + : ['array', 'max:'.$maxValues]; + } +} diff --git a/src/Filament/Integration/Concerns/Shared/ConfiguresBadgeColors.php b/src/Filament/Integration/Concerns/Shared/ConfiguresBadgeColors.php index 7fc214d7..284fbe31 100644 --- a/src/Filament/Integration/Concerns/Shared/ConfiguresBadgeColors.php +++ b/src/Filament/Integration/Concerns/Shared/ConfiguresBadgeColors.php @@ -4,14 +4,22 @@ namespace Relaticle\CustomFields\Filament\Integration\Concerns\Shared; +use Filament\Infolists\Components\TextEntry; use Filament\Support\Colors\Color; +use Filament\Tables\Columns\TextColumn; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Models\CustomField; trait ConfiguresBadgeColors { - protected function applyBadgeColorsIfEnabled($component, CustomField $customField) + /** + * @template TComponent of TextEntry|TextColumn + * + * @param TComponent $component + * @return TComponent + */ + protected function applyBadgeColorsIfEnabled(TextEntry|TextColumn $component, CustomField $customField): TextEntry|TextColumn { if ($customField->typeData->acceptsArbitraryValues) { return $this->applyTagsBadgeColors($component, $customField); @@ -22,7 +30,7 @@ protected function applyBadgeColorsIfEnabled($component, CustomField $customFiel } return $component->badge() - ->color(function ($state) use ($customField): array { + ->color(function (mixed $state) use ($customField): array { $color = $customField->options->where('name', $state)->first()?->settings->color; return Color::hex($color ?? '#000000'); @@ -32,11 +40,16 @@ protected function applyBadgeColorsIfEnabled($component, CustomField $customFiel /** * Apply badge styling for tags (fields with arbitrary values). * Always displays as badges with predefined option colors or gray fallback. + * + * @template TComponent of TextEntry|TextColumn + * + * @param TComponent $component + * @return TComponent */ - private function applyTagsBadgeColors($component, CustomField $customField) + private function applyTagsBadgeColors(TextEntry|TextColumn $component, CustomField $customField): TextEntry|TextColumn { return $component->badge() - ->color(function ($state) use ($customField): array|string { + ->color(function (mixed $state) use ($customField): array|string { if ($this->shouldApplyBadgeColors($customField)) { $option = $customField->options->where('name', $state)->first(); @@ -53,6 +66,6 @@ private function shouldApplyBadgeColors(CustomField $customField): bool { return FeatureManager::isEnabled(CustomFieldsFeature::FIELD_OPTION_COLORS) && $customField->settings->enable_option_colors - && ! $customField->lookup_type; + && ! $customField->typeData->requiresRelationship; } } diff --git a/src/Filament/Integration/Factories/AbstractComponentFactory.php b/src/Filament/Integration/Factories/AbstractComponentFactory.php index cc71a683..6d31f904 100644 --- a/src/Filament/Integration/Factories/AbstractComponentFactory.php +++ b/src/Filament/Integration/Factories/AbstractComponentFactory.php @@ -4,6 +4,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Factories; +use Closure; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\Container\Container; use InvalidArgumentException; @@ -39,8 +40,10 @@ public function __construct( ) {} /** - * Create component instance for given field. - * Supports both traditional class-based components and modern inline Closure components. + * Resolve a class-based component instance for the given field. + * Closure-based components are not resolvable here: a Closure carries no class to + * instantiate against $expectedInterface, so concrete factories must detect and adapt + * them (see FieldComponentFactory + ClosureFormAdapter) before calling this method. * * @throws BindingResolutionException * @throws InvalidArgumentException @@ -66,7 +69,10 @@ protected function createComponent(CustomField $customField, string $componentKe throw new InvalidArgumentException(sprintf('Field type "%s" does not support %s', $customField->type, $componentKey)); } - // Handle traditional component class + if ($componentDefinition instanceof Closure) { + throw new InvalidArgumentException(sprintf('Component key "%s" for field type "%s" resolved to a Closure; %s only resolves class-based components, the caller must adapt closures first', $componentKey, $customField->type, static::class)); + } + if (! class_exists($componentDefinition)) { throw new InvalidArgumentException(sprintf('Component class not found for %s of type %s', $componentKey, $customField->type)); } diff --git a/src/Filament/Integration/Factories/ExportColumnFactory.php b/src/Filament/Integration/Factories/ExportColumnFactory.php index 18e8540c..0e15387b 100644 --- a/src/Filament/Integration/Factories/ExportColumnFactory.php +++ b/src/Filament/Integration/Factories/ExportColumnFactory.php @@ -5,7 +5,7 @@ namespace Relaticle\CustomFields\Filament\Integration\Factories; use Filament\Actions\Exports\ExportColumn; -use Relaticle\CustomFields\Contracts\ValueResolvers; +use Relaticle\CustomFields\Contracts\ValueResolverInterface; use Relaticle\CustomFields\Models\CustomField; /** @@ -15,7 +15,7 @@ final readonly class ExportColumnFactory { public function __construct( - private ValueResolvers $valueResolver + private ValueResolverInterface $valueResolver ) {} public function create(CustomField $customField): ExportColumn diff --git a/src/Filament/Integration/Factories/FieldComponentFactory.php b/src/Filament/Integration/Factories/FieldComponentFactory.php index df21e472..a354528e 100644 --- a/src/Filament/Integration/Factories/FieldComponentFactory.php +++ b/src/Filament/Integration/Factories/FieldComponentFactory.php @@ -41,13 +41,6 @@ public function create(CustomField $customField, array $dependentFieldCodes = [] $component = $this->createComponent($customField, 'form_component', FormComponentInterface::class); } - // Only AbstractFormComponent consumes the optional $record (server-side relation-attribute - // visibility). Third-party FormComponentInterface implementers keep the original 3-arg contract, - // so the package stays backward compatible for that public extension point. - if ($component instanceof AbstractFormComponent) { - return $component->make($customField, $dependentFieldCodes, $allFields, $record); - } - - return $component->make($customField, $dependentFieldCodes, $allFields); + return $component->make($customField, $dependentFieldCodes, $allFields, $record); } } diff --git a/src/Filament/Integration/Factories/FieldFilterFactory.php b/src/Filament/Integration/Factories/FieldFilterFactory.php index 5cf18e71..e6942022 100644 --- a/src/Filament/Integration/Factories/FieldFilterFactory.php +++ b/src/Filament/Integration/Factories/FieldFilterFactory.php @@ -15,7 +15,7 @@ final class FieldFilterFactory /** * @throws BindingResolutionException */ - public function create(CustomField $customField): BaseFilter + public function create(CustomField $customField, ?string $through = null): BaseFilter { $tableFilterDefinition = $customField->typeData->tableFilter; @@ -25,12 +25,12 @@ public function create(CustomField $customField): BaseFilter // Handle inline component (Closure) if ($tableFilterDefinition instanceof Closure) { - return $tableFilterDefinition($customField); + return $tableFilterDefinition($customField, $through); } // Handle traditional component class $component = app($tableFilterDefinition); - return $component->make($customField); + return $component->make($customField, null, $through); } } diff --git a/src/Filament/Integration/Migrations/CustomFieldsMigration.php b/src/Filament/Integration/Migrations/CustomFieldsMigration.php index 6430aa7f..9314b9cf 100644 --- a/src/Filament/Integration/Migrations/CustomFieldsMigration.php +++ b/src/Filament/Integration/Migrations/CustomFieldsMigration.php @@ -5,16 +5,15 @@ namespace Relaticle\CustomFields\Filament\Integration\Migrations; use Illuminate\Database\Migrations\Migration; -use Relaticle\CustomFields\Contracts\CustomsFieldsMigrators; abstract class CustomFieldsMigration extends Migration { - protected CustomsFieldsMigrators $migrator; + protected CustomFieldsMigrator $migrator; abstract public function up(): void; public function __construct() { - $this->migrator = app(CustomsFieldsMigrators::class); + $this->migrator = app(CustomFieldsMigrator::class); } } diff --git a/src/Filament/Integration/Migrations/CustomFieldsMigrator.php b/src/Filament/Integration/Migrations/CustomFieldsMigrator.php index 07c7d265..3a75c1f2 100644 --- a/src/Filament/Integration/Migrations/CustomFieldsMigrator.php +++ b/src/Filament/Integration/Migrations/CustomFieldsMigrator.php @@ -5,12 +5,17 @@ namespace Relaticle\CustomFields\Filament\Integration\Migrations; use Exception; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; -use Relaticle\CustomFields\Contracts\CustomsFieldsMigrators; +use InvalidArgumentException; use Relaticle\CustomFields\CustomFields; use Relaticle\CustomFields\Data\CustomFieldData; +use Relaticle\CustomFields\Data\CustomFieldOptionSettingsData; use Relaticle\CustomFields\Data\CustomFieldSectionData; +use Relaticle\CustomFields\Data\FieldSlotData; +use Relaticle\CustomFields\Data\RelationshipDefinitionData; use Relaticle\CustomFields\Enums\CustomFieldsFeature; +use Relaticle\CustomFields\Enums\RelationshipCardinality; use Relaticle\CustomFields\Exceptions\CustomFieldAlreadyExistsException; use Relaticle\CustomFields\Exceptions\CustomFieldDoesNotExistException; use Relaticle\CustomFields\Exceptions\FieldTypeNotOptionableException; @@ -18,12 +23,20 @@ use Relaticle\CustomFields\Facades\Entities; use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldRelationship; +use Relaticle\CustomFields\Services\Relationships\CreateRelationshipDefinition; +use Relaticle\CustomFields\Services\TenantContextService; +use Relaticle\CustomFields\Support\CodeGenerator; use Throwable; -class CustomFieldsMigrator implements CustomsFieldsMigrators +final class CustomFieldsMigrator { private int|string|null $tenantId = null; + private ?string $targetEntityType = null; + + private ?RelationshipCardinality $cardinality = null; + private CustomFieldData $customFieldData; private ?CustomField $customField = null; @@ -33,6 +46,9 @@ public function setTenantId(int|string|null $tenantId = null): void $this->tenantId = $tenantId; } + /** + * @param class-string $model + */ public function find(string $model, string $code): CustomFieldsMigrator { $this->customField = CustomFields::newCustomFieldModel() @@ -67,6 +83,8 @@ public function new( } /** + * @param array $options + * * @throws FieldTypeNotOptionableException */ public function options(array $options): CustomFieldsMigrator @@ -81,15 +99,22 @@ public function options(array $options): CustomFieldsMigrator } /** + * Point a record field at another entity. The field becomes the single slot of a one-way + * relationship definition, created with the field. Without a cardinality, allow_multiple + * on the field data picks it, exactly as the 4.0 upgrade step does. + * + * @param class-string $model + * * @throws FieldTypeNotOptionableException */ - public function lookupType(string $model): CustomFieldsMigrator + public function lookupType(string $model, ?RelationshipCardinality $cardinality = null): CustomFieldsMigrator { if (! $this->isCustomFieldTypeOptionable()) { throw new FieldTypeNotOptionableException; } - $this->customFieldData->lookupType = (Entities::getEntity($model)?->getAlias()) ?? $model; + $this->targetEntityType = (Entities::getEntity($model)?->getAlias()) ?? $model; + $this->cardinality = $cardinality; return $this; } @@ -160,6 +185,10 @@ public function create(): CustomField ); } + if ($this->targetEntityType !== null) { + $this->defineRelationship($customField); + } + DB::commit(); return $customField; @@ -170,6 +199,8 @@ public function create(): CustomField } /** + * @param array $data + * * @throws CustomFieldDoesNotExistException|Throwable */ public function update(array $data): void @@ -180,6 +211,10 @@ public function update(array $data): void ); } + if (array_key_exists('lookup_type', $data)) { + throw new InvalidArgumentException('The ends of a relationship are locked after it is created.'); + } + try { DB::beginTransaction(); @@ -266,9 +301,37 @@ public function deactivate(): void } /** - * @param array $options + * The migrator stamps its own tenant on every row it writes, so the definition service + * gets that tenant as its context rather than whatever the ambient one happens to be. + */ + private function defineRelationship(CustomField $customField): void + { + $data = new RelationshipDefinitionData( + code: CodeGenerator::generateUniqueRelationshipCode($customField->code), + fromEntityType: (string) $customField->entity_type, + toEntityType: (string) $this->targetEntityType, + cardinality: $this->cardinality ?? $this->cardinalityFromSettings(), + fromField: new FieldSlotData(name: $customField->name, fieldId: $customField->getKey()), + ); + + $define = fn (): CustomFieldRelationship => app(CreateRelationshipDefinition::class)->execute($data); + + $this->tenantId === null + ? $define() + : TenantContextService::withTenant($this->tenantId, $define); + } + + private function cardinalityFromSettings(): RelationshipCardinality + { + return $this->customFieldData->settings?->allow_multiple === true + ? RelationshipCardinality::ManyToMany + : RelationshipCardinality::ManyToOne; + } + + /** + * @param array $options */ - protected function createOptions( + private function createOptions( CustomField $customField, array $options ): void { @@ -280,6 +343,13 @@ protected function createOptions( 'sort_order' => $key, ]; + if (is_array($value)) { + $this->assertOptionIsSettable($value); + + $data['name'] = $value['name']; + $data['settings'] = CustomFieldOptionSettingsData::from(Arr::except($value, 'name')); + } + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { $data[config( 'custom-fields.database.column_names.tenant_foreign_key' @@ -292,7 +362,7 @@ protected function createOptions( ); } - protected function isCustomFieldExists( + private function isCustomFieldExists( string $model, string $code, int|string|null $tenantId = null @@ -311,7 +381,40 @@ protected function isCustomFieldExists( ->exists(); } - protected function isCustomFieldTypeOptionable(): bool + /** + * @param array $option + */ + private function assertOptionIsSettable(array $option): void + { + $code = $this->customFieldData->code; + + if (! isset($option['name']) || ! is_string($option['name'])) { + throw new InvalidArgumentException(sprintf('Every option array on [%s] must carry a name.', $code)); + } + + $unknownKeys = array_diff( + array_keys($option), + ['name', ...array_keys(CustomFieldOptionSettingsData::empty())], + ); + + if ($unknownKeys !== []) { + throw new InvalidArgumentException( + sprintf('Option [%s] on [%s] carries unknown keys: ', $option['name'], $code).implode(', ', $unknownKeys).'.' + ); + } + + if (! isset($option['category'])) { + return; + } + + if (CustomFieldsType::getFieldType($this->customFieldData->type)?->carriesOptionCategories !== true) { + throw new InvalidArgumentException( + sprintf('Option [%s] carries a category, but the options of [%s] are not workflow states.', $option['name'], $code) + ); + } + } + + private function isCustomFieldTypeOptionable(): bool { return CustomFieldsType::getFieldType($this->customFieldData->type)->dataType->isChoiceField(); } diff --git a/src/Filament/Integration/Support/Imports/ImportColumnConfigurator.php b/src/Filament/Integration/Support/Imports/ImportColumnConfigurator.php index b5155633..efe2a312 100644 --- a/src/Filament/Integration/Support/Imports/ImportColumnConfigurator.php +++ b/src/Filament/Integration/Support/Imports/ImportColumnConfigurator.php @@ -10,7 +10,10 @@ use Carbon\CarbonImmutable; use Closure; use Filament\Actions\Imports\ImportColumn; +use Illuminate\Database\Eloquent\Model; +use InvalidArgumentException; use Relaticle\CustomFields\CustomFields; +use Relaticle\CustomFields\Data\EntityConfigurationData; use Relaticle\CustomFields\Enums\FieldDataType; use Relaticle\CustomFields\Facades\CustomFieldsType; use Relaticle\CustomFields\Facades\Entities; @@ -87,7 +90,7 @@ private function configureViaFieldType(ImportColumn $column, CustomField $custom private function configureSingleChoice(ImportColumn $column, CustomField $customField): void { // Lookup fields (Record type) handle entity references - if ($customField->typeData->requiresLookupType) { + if ($customField->typeData->requiresRelationship) { $this->configureLookup($column, $customField, false); } else { $this->configureChoices($column, $customField, false); @@ -118,7 +121,7 @@ private function configureMultiChoice(ImportColumn $column, CustomField $customF $column->example('tag1, tag2, tag3'); $column->helperText(__('custom-fields::custom-fields.import.multi_value_helper')); - } elseif ($customField->typeData->requiresLookupType) { + } elseif ($customField->typeData->requiresRelationship) { // Lookup fields (Record type) handle entity references $this->configureLookup($column, $customField, true); } else { @@ -154,7 +157,7 @@ private function configureLookup(ImportColumn $column, CustomField $customField, private function resolveLookupValue(CustomField $customField, mixed $value): int|UnresolvedValue { try { - $entity = Entities::getEntity($customField->lookup_type); + $entity = $this->targetEntity($customField); $modelInstance = $entity->createModelInstance(); $primaryAttribute = $entity->getPrimaryAttribute(); @@ -189,6 +192,9 @@ private function resolveLookupValue(CustomField $customField, mixed $value): int /** * Resolve multiple lookup values. + * + * @param array $values + * @return array|UnresolvedValue */ private function resolveLookupValues(CustomField $customField, array $values): array|UnresolvedValue { @@ -221,9 +227,23 @@ private function resolveLookupValues(CustomField $customField, array $values): a private function lookupRecordLabel(CustomField $customField): string { - return filled($customField->lookup_type) - ? $customField->lookup_type.' record' - : 'record'; + $entityType = $customField->targetEntityType(); + + return $entityType === null + ? 'record' + : $entityType.' record'; + } + + /** + * The entity a record field points at. Both callers translate the failure into an + * unresolved value or a generic example, so an unusable field never aborts the import. + */ + private function targetEntity(CustomField $customField): EntityConfigurationData + { + $entityType = $customField->targetEntityType(); + $entity = $entityType === null ? null : Entities::getEntity($entityType); + + return $entity ?? throw new InvalidArgumentException(sprintf('Record field [%s] points at no entity.', $customField->code)); } /** @@ -282,6 +302,10 @@ private function resolveChoiceValue(CustomField $customField, mixed $value): int return CustomFields::optionModelUsesStringKeys() ? (string) $key : $key; } + /** + * @param array $values + * @return array|UnresolvedValue + */ private function resolveChoiceValues(CustomField $customField, array $values): array|UnresolvedValue { $foundIds = []; @@ -453,7 +477,7 @@ private function configureText(ImportColumn $column, CustomField $customField): private function setLookupExamples(ImportColumn $column, CustomField $customField, bool $multiple): void { try { - $entity = Entities::getEntity($customField->lookup_type); + $entity = $this->targetEntity($customField); $modelInstance = $entity->createModelInstance(); $primaryAttribute = $entity->getPrimaryAttribute(); @@ -510,10 +534,13 @@ private function setChoiceExamples(ImportColumn $column, CustomField $customFiel */ private function finalize(ImportColumn $column, CustomField $customField): ImportColumn { - $column->rules([ + // The row's record is resolved before its data is validated, so a rule that has to + // know which record is being updated (a unique value, a taken relationship end) is + // told, instead of reading every existing value as a stranger's. + $column->rules(fn (?Model $record): array => [ 'bail', new RejectsUnresolvedValue, - ...app(ValidationService::class)->getValidationRules($customField), + ...app(ValidationService::class)->getValidationRules($customField, $record?->getKey()), ]); $column->fillRecordUsing(function (mixed $state, mixed $record) use ($customField): void { diff --git a/src/Filament/Integration/Support/Imports/ImportDataStorage.php b/src/Filament/Integration/Support/Imports/ImportDataStorage.php index 7d214e40..c30f670f 100644 --- a/src/Filament/Integration/Support/Imports/ImportDataStorage.php +++ b/src/Filament/Integration/Support/Imports/ImportDataStorage.php @@ -21,6 +21,8 @@ final class ImportDataStorage /** * WeakMap storage for custom field data during import. * Automatically cleans up when model instances are garbage collected. + * + * @var ?WeakMap> */ private static ?WeakMap $storage = null; diff --git a/src/Filament/Integration/Support/RecordChips.php b/src/Filament/Integration/Support/RecordChips.php new file mode 100644 index 00000000..5f1cc67e --- /dev/null +++ b/src/Filament/Integration/Support/RecordChips.php @@ -0,0 +1,213 @@ + $recordIds the order the chips are drawn in + * @param array $provenance chip id => the sentence read on hover + * @return array + */ + public function build(?EntityConfigurationData $entity, array $recordIds, array $provenance = []): array + { + if (! $entity instanceof EntityConfigurationData || $recordIds === []) { + return []; + } + + $records = $entity->newQuery() + ->whereKey($recordIds) + ->get() + ->sortBy(fn (Model $record): int|false => array_search($record->getKey(), $recordIds, true)); + + $avatarConfiguration = $entity->getAvatarConfiguration(); + $titleAttribute = $entity->getPrimaryAttribute(); + + return $records + ->map(function (Model $record) use ($entity, $avatarConfiguration, $titleAttribute, $provenance): array { + $id = (string) $record->getKey(); + $name = $record->getAttribute($titleAttribute); + + return [ + 'id' => $id, + 'name' => is_scalar($name) ? (string) $name : '', + 'avatarUrl' => $this->avatarUrl($record, $avatarConfiguration), + 'avatarShape' => $avatarConfiguration?->getCssClass() ?? 'rounded-full', + 'url' => $this->recordUrl($record, $entity), + 'provenance' => $provenance[$id] ?? null, + ]; + }) + ->values() + ->all(); + } + + /** + * Where each link came from, keyed by the record on the far end. Read from rows the caller + * already holds: an actor name needs the morph loaded, and a table page that has not + * loaded it says when the link was made rather than paying a query per row for who. + * + * @return array + */ + public function provenance(Model $subject, CustomField $customField): array + { + $definition = $customField->relationshipDefinition(); + + // Provenance is read on a chip's hover, and only a paired relationship draws chips. + if (! $customField->supportsPairing() || ! $definition instanceof CustomFieldRelationship) { + return []; + } + + $direction = $definition->readDirectionFor($customField); + $provenance = []; + + foreach ($this->linkReader->orderedLinksFor($subject, $definition, $direction) as $link) { + $id = (string) $this->linkReader->otherEndId($link, $subject, $direction); + $sentence = $this->sentence($link); + + if ($sentence !== null) { + $provenance[$id] = $sentence; + } + } + + return $provenance; + } + + /** + * The page a resource opens the record on, or null when the host registered no resource. + * A named page the resource does not define is a configuration mistake, not a missing + * link, so it is reported rather than swallowed. + */ + public function recordUrl(Model $record, EntityConfigurationData $entity): ?string + { + $recordPage = $entity->getRecordPage(); + $resourceClass = $entity->getResourceClass(); + + if ($recordPage === null || $resourceClass === null || ! class_exists($resourceClass)) { + return null; + } + + if (! method_exists($resourceClass, 'getUrl') || ! method_exists($resourceClass, 'getPages')) { + return null; + } + + if (! array_key_exists($recordPage, $resourceClass::getPages())) { + throw new InvalidArgumentException(sprintf( + "Entity '%s' has recordPage '%s' but %s does not define a '%s' page. Available pages: %s.", + $entity->getLabelSingular(), + $recordPage, + class_basename($resourceClass), + $recordPage, + implode(', ', array_keys($resourceClass::getPages())), + )); + } + + return $resourceClass::getUrl($recordPage, ['record' => $record]); + } + + /** + * The page that creates a record of the target entity, for the picker's create-new. A host + * whose resource has no create page gets no create-new rather than a dead link. + */ + public function createUrl(EntityConfigurationData $entity): ?string + { + $resourceClass = $entity->getResourceClass(); + + if ($resourceClass === null || ! class_exists($resourceClass)) { + return null; + } + + if (! method_exists($resourceClass, 'getUrl') || ! method_exists($resourceClass, 'getPages')) { + return null; + } + + return array_key_exists('create', $resourceClass::getPages()) + ? $resourceClass::getUrl('create') + : null; + } + + private function sentence(CustomFieldLink $link): ?string + { + $when = $link->active_from?->diffForHumans(); + + if ($when === null) { + return null; + } + + $actor = $this->actorName($link); + + if ($actor !== null) { + return __('custom-fields::custom-fields.relationships.provenance.by_actor', [ + 'actor' => $actor, + 'time' => $when, + ]); + } + + return __('custom-fields::custom-fields.relationships.provenance.by_source', [ + 'source' => $this->sourceLabel($link->source), + 'time' => $when, + ]); + } + + /** + * A host writes its own source strings onto the ledger, so an unknown one reads as itself + * rather than as the lang key that has no translation. + */ + private function sourceLabel(string $source): string + { + $key = 'custom-fields::custom-fields.relationships.sources.'.$source; + + return Lang::has($key) ? __($key) : $source; + } + + private function actorName(CustomFieldLink $link): ?string + { + if (! $link->relationLoaded('createdBy')) { + return null; + } + + $actor = $link->getRelation('createdBy'); + + if (! $actor instanceof Model) { + return null; + } + + if ($actor instanceof HasName) { + return $actor->getFilamentName(); + } + + $name = $actor->getAttribute('name'); + + return is_scalar($name) && (string) $name !== '' ? (string) $name : null; + } + + private function avatarUrl(Model $record, ?AvatarConfiguration $avatarConfiguration): ?string + { + if (! $avatarConfiguration instanceof AvatarConfiguration || ! $avatarConfiguration->hasAttribute()) { + return null; + } + + $url = $record->getAttribute($avatarConfiguration->attribute); + + return is_scalar($url) && (string) $url !== '' ? (string) $url : null; + } +} diff --git a/src/Filament/Management/Forms/Components/DateConstraintField.php b/src/Filament/Management/Forms/Components/DateConstraintField.php index 195a7711..a1bf3730 100644 --- a/src/Filament/Management/Forms/Components/DateConstraintField.php +++ b/src/Filament/Management/Forms/Components/DateConstraintField.php @@ -13,6 +13,7 @@ use Filament\Schemas\Components\Fieldset; use Filament\Schemas\Components\Utilities\Get; use Filament\Schemas\Components\Utilities\Set; +use Illuminate\Database\Eloquent\Builder; use Relaticle\CustomFields\CustomFields; use Relaticle\CustomFields\Enums\DateAnchor; use Relaticle\CustomFields\Enums\DateOffsetDirection; @@ -35,7 +36,7 @@ public static function make(string $statePath, string $label, string $context = ->options(self::presetOptions($context)) ->default('none') ->live() - ->afterStateHydrated(function ($component, Get $get) use ($statePath): void { + ->afterStateHydrated(function (Component $component, Get $get) use ($statePath): void { $anchor = $get($statePath.'.anchor'); if ($anchor === null) { @@ -95,7 +96,7 @@ public static function make(string $statePath, string $label, string $context = return CustomFields::newCustomFieldModel()::query() ->where('entity_type', $entityType) ->whereIn('type', ['date', 'date-time']) - ->when($currentCode, fn ($q) => $q->where('code', '!=', $currentCode)) + ->when($currentCode, fn (Builder $q): Builder => $q->where('code', '!=', $currentCode)) ->where('active', true) ->pluck('name', 'code') ->all(); diff --git a/src/Filament/Management/Forms/Components/RelationshipConfigurator.php b/src/Filament/Management/Forms/Components/RelationshipConfigurator.php new file mode 100644 index 00000000..ef56600f --- /dev/null +++ b/src/Filament/Management/Forms/Components/RelationshipConfigurator.php @@ -0,0 +1,147 @@ +configure(); + + return $static; + } + + protected function setUp(): void + { + parent::setUp(); + + $polishedView = ViewFlavor::view(UiSurface::RelationshipConfigurator); + + if ($polishedView !== null) { + $this->view($polishedView); + } + + $this->columnSpanFull(); + } + + /** + * The children by name, so the view places each one instead of counting on an order. A + * component the shared closures hide is absent here too, which is how the flavor stays + * presentation: visibility is decided once, in PHP, for both flavors. + * + * @return array + */ + public function getConfiguredFields(): array + { + $fields = []; + + foreach ($this->getChildSchema()?->getComponents() ?? [] as $component) { + if ($component instanceof Field) { + $fields[$component->getName()] = $component; + } + } + + return $fields; + } + + public function getSourceEntity(): ?EntityConfigurationData + { + return $this->entity($this->stateAt('entity_type')); + } + + public function getTargetEntity(): ?EntityConfigurationData + { + return $this->entity($this->stateAt('relationship.target_entity_type')); + } + + /** + * The cardinality in words, with both entity names in it: the control between the two + * cards has to read as the sentence it is choosing. + */ + public function getCardinalitySentence(): ?string + { + $cardinality = RelationshipCardinality::tryFrom((string) $this->stateAt('relationship.cardinality')); + $source = $this->getSourceEntity(); + $target = $this->getTargetEntity(); + + if (! $cardinality instanceof RelationshipCardinality || ! $source instanceof EntityConfigurationData || ! $target instanceof EntityConfigurationData) { + return null; + } + + // The count in front of each name is the case's own name read left to right, not the + // end constraint that shares the word: many_to_one holds one record per source. + [$sourceIsPlural, $targetIsPlural] = match ($cardinality) { + RelationshipCardinality::OneToOne => [false, false], + RelationshipCardinality::OneToMany => [false, true], + RelationshipCardinality::ManyToOne => [true, false], + RelationshipCardinality::ManyToMany => [true, true], + }; + + // A host resource label can be lowercase for Filament's sentence use, and both names + // sit mid-sentence here only after a count word that opens it. + return __('custom-fields::custom-fields.field.form.record.sentence.'.$cardinality->value, [ + 'source' => Str::ucfirst($sourceIsPlural ? $source->getLabelPlural() : $source->getLabelSingular()), + 'target' => Str::ucfirst($targetIsPlural ? $target->getLabelPlural() : $target->getLabelSingular()), + ]); + } + + /** + * The name input belongs to every field type, so it stays in the shared grid above; the + * source card mirrors it live rather than holding a second input over the same state. + */ + public function getFieldNameStatePath(): string + { + return $this->resolveRelativeStatePath('name'); + } + + public function getFieldName(): string + { + $name = $this->stateAt('name'); + + return is_string($name) && trim($name) !== '' + ? $name + : __('custom-fields::custom-fields.field.form.record.untitled_field'); + } + + public function isSymmetric(): bool + { + return $this->stateAt('relationship.is_symmetric') === true; + } + + public function pairsAField(): bool + { + return ! $this->isSymmetric() + && filled($this->stateAt('relationship.paired_field_name')); + } + + private function entity(mixed $entityType): ?EntityConfigurationData + { + if (! is_string($entityType) || $entityType === '') { + return null; + } + + return Entities::getEntity($entityType); + } + + private function stateAt(string $path): mixed + { + return $this->evaluate(fn (Get $get): mixed => $get($path)); + } +} diff --git a/src/Filament/Management/Forms/Components/TypeField.php b/src/Filament/Management/Forms/Components/TypeField.php index 1b46914e..7e45e92a 100644 --- a/src/Filament/Management/Forms/Components/TypeField.php +++ b/src/Filament/Management/Forms/Components/TypeField.php @@ -6,10 +6,13 @@ use Filament\Forms\Components\Select; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Lang; use Relaticle\CustomFields\Data\FieldTypeData; +use Relaticle\CustomFields\Enums\UiSurface; use Relaticle\CustomFields\Facades\CustomFieldsType; +use Relaticle\CustomFields\Support\ViewFlavor; -class TypeField extends Select +final class TypeField extends Select { /** * Set up the component with a custom configuration. @@ -18,6 +21,14 @@ protected function setUp(): void { parent::setUp(); + $polishedView = ViewFlavor::view(UiSurface::TypePicker); + + if ($polishedView !== null) { + + $this->view($polishedView); + + } + $this->native(false) ->allowHtml() ->searchable() @@ -30,6 +41,48 @@ protected function setUp(): void ->options(fn (): array => $this->getAllFormattedOptions()); } + /** + * Every field type as the grid draws it: what it is called, what it looks like, and one + * line saying what it is for. A type a host registered without a description keeps its + * label rather than showing an empty line. + * + * @return array + */ + public function getTypeChoices(): array + { + $choices = []; + + // The grid draws the Select's own options, not the registry: a consumer that narrows + // ->options() or disables one with ->disableOptionWhen() has to narrow both flavors. + foreach (array_keys($this->getEnabledOptions()) as $key) { + $data = CustomFieldsType::getFieldType((string) $key); + + if (! $data instanceof FieldTypeData) { + continue; + } + + $choices[] = [ + 'key' => $data->key, + 'label' => $data->label, + 'icon' => $data->icon, + 'description' => $this->description($data), + ]; + } + + return $choices; + } + + /** + * Type keys are hyphenated and lang keys are not, the same way the type labels already + * resolve, so a description is found under the key its label uses. + */ + private function description(FieldTypeData $data): ?string + { + $key = 'custom-fields::custom-fields.field_type_descriptions.'.str_replace('-', '_', $data->key); + + return Lang::has($key) ? __($key) : null; + } + /** * Get all formatted options. * diff --git a/src/Filament/Management/Forms/Components/Visibility/ConditionOptions.php b/src/Filament/Management/Forms/Components/Visibility/ConditionOptions.php new file mode 100644 index 00000000..de1d9f5f --- /dev/null +++ b/src/Filament/Management/Forms/Components/Visibility/ConditionOptions.php @@ -0,0 +1,270 @@ + + */ + public function getAvailableSourceOptions(Get $get): array + { + $entityType = $this->getEntityType($get); + + $options = [ + ConditionSource::CustomField->value => ConditionSource::CustomField->getLabel(), + ]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::MODEL_ATTRIBUTE_CONDITIONS)) { + $options[ConditionSource::ModelAttribute->value] = ConditionSource::ModelAttribute->getLabel(); + } + + if (! blank($entityType) && app(RelationConditionConfig::class)->isRelationSourceAvailable($entityType)) { + $options[ConditionSource::RelationAttribute->value] = ConditionSource::RelationAttribute->getLabel(); + } + + return $options; + } + + private function sourceIs(Get $get, ConditionSource $expected): bool + { + $source = $get('source'); + + if ($source instanceof ConditionSource) { + return $source === $expected; + } + + return $source === $expected->value; + } + + public function isModelAttributeSource(Get $get): bool + { + return $this->sourceIs($get, ConditionSource::ModelAttribute); + } + + public function isRelationAttributeSource(Get $get): bool + { + return $this->sourceIs($get, ConditionSource::RelationAttribute); + } + + /** + * @return array + */ + public function getAvailableFields(Get $get): array + { + $entityType = $this->getEntityType($get); + if (blank($entityType)) { + return []; + } + + if ($this->isRelationAttributeSource($get)) { + return app(RelationConditionConfig::class)->relationsFor($entityType); + } + + if ($this->isModelAttributeSource($get)) { + return rescue( + fn (): array => app(ModelAttributeDiscoveryService::class)->getAttributeOptions($entityType), + [] + ); + } + + $currentFieldCode = $this->forSection ? null : $get('../../../../code'); + $scopeSection = $this->scopeSection; + $scopeResolver = self::$availableFieldsScopeResolver; + + return rescue(function () use ($entityType, $currentFieldCode, $scopeSection, $scopeResolver) { + $query = CustomFields::customFieldModel()::query() + ->forMorphEntity($entityType) + ->when($currentFieldCode, fn (mixed $query) => $query->where('code', '!=', $currentFieldCode)); + + if ($scopeResolver instanceof Closure) { + $constraint = $scopeResolver($entityType, $scopeSection); + + if ($constraint instanceof Closure) { + $query = $constraint($query) ?? $query; + } + } + + return $query->orderBy('name') + ->pluck('name', 'code') + ->toArray(); + }, []); + } + + /** + * @return array + */ + public function getCompatibleOperators(Get $get): array + { + if ($this->isRelationAttributeSource($get)) { + return [ + VisibilityOperator::IS_IN->value => VisibilityOperator::IS_IN->getLabel(), + VisibilityOperator::IS_NOT_IN->value => VisibilityOperator::IS_NOT_IN->getLabel(), + ]; + } + + if ($this->isModelAttributeSource($get)) { + return collect(VisibilityOperator::options()) + ->except([VisibilityOperator::IS_IN->value, VisibilityOperator::IS_NOT_IN->value]) + ->all(); + } + + $fieldData = $this->getFieldTypeData($get); + + return $fieldData + ? $fieldData->getCompatibleOperatorOptions() + : collect(VisibilityOperator::options()) + ->except([VisibilityOperator::IS_IN->value, VisibilityOperator::IS_NOT_IN->value]) + ->all(); + } + + /** + * @return array + */ + public function getFieldOptions(Get $get): array + { + if ($this->isModelAttributeSource($get)) { + return []; + } + + $fieldCode = $get('field_code'); + if (blank($fieldCode)) { + return []; + } + + $entityType = $this->getEntityType($get); + if (blank($entityType)) { + return []; + } + + return rescue(function () use ($fieldCode, $entityType) { + return app(BackendVisibilityService::class) + ->getFieldOptions($fieldCode, $entityType); + }, []); + } + + /** + * @return array + */ + public function getRelationValueOptions(Get $get): array + { + $path = $get('field_code'); + + if (blank($path)) { + return []; + } + + $entityType = $this->getEntityType($get); + if (blank($entityType)) { + return []; + } + + $related = app(RelationConditionResolver::class)->resolveTerminalRelatedModel($entityType, (string) $path); + + if (! $related instanceof Model) { + return []; + } + + static $labelColumns = []; + $modelClass = $related::class; + if (! isset($labelColumns[$modelClass])) { + $labelColumns[$modelClass] = collect(['name', 'title', 'label']) + ->first(fn (string $column): bool => $related->getConnection()->getSchemaBuilder()->hasColumn($related->getTable(), $column)) + ?? $related->getKeyName(); + } + + $labelColumn = $labelColumns[$modelClass]; + + return $related::query()->pluck($labelColumn, $related->getKeyName())->all(); + } + + public function getFieldTypeData(Get $get): ?object + { + $fieldCode = $get('field_code'); + if (blank($fieldCode)) { + return null; + } + + $field = $this->getCustomField($fieldCode, $get); + if (! $field instanceof CustomField) { + return null; + } + + return rescue( + fn () => CustomFieldsType::getFieldType($field->type) + ); + } + + private function getCustomField(string $fieldCode, Get $get): ?CustomField + { + $entityType = $this->getEntityType($get); + if (blank($entityType)) { + return null; + } + + return rescue(function () use ($entityType, $fieldCode) { + return CustomFields::customFieldModel()::query() + ->forMorphEntity($entityType) + ->where('code', $fieldCode) + ->first(); + }); + } + + public function getEntityType(?Get $get = null): ?string + { + if ($this->forSection && $this->sectionEntityType) { + return $this->sectionEntityType; + } + + return ($get instanceof Get ? $get('../../../../entity_type') : null) + ?? request('entityType') + ?? request()->route('entityType'); + } +} diff --git a/src/Filament/Management/Forms/Components/Visibility/ConditionRow.php b/src/Filament/Management/Forms/Components/Visibility/ConditionRow.php new file mode 100644 index 00000000..af2669fd --- /dev/null +++ b/src/Filament/Management/Forms/Components/Visibility/ConditionRow.php @@ -0,0 +1,330 @@ + + * + * @throws Exception + */ + public function components(): array + { + $schema = []; + + $schema[] = Select::make('source') + ->label(__('custom-fields::custom-fields.visibility.source')) + ->options(fn (Get $get): array => $this->options->getAvailableSourceOptions($get)) + ->default(ConditionSource::CustomField->value) + ->required() + ->live() + ->afterStateUpdated(fn (Set $set) => $this->resetConditionValues(null, $set)) + // Show the source picker only when more than the default CustomField source is available + // (model-attribute flag on, or the entity has configured relation paths). Decided per-render + // via $get so it works in Livewire action contexts where the entity is not known at build time. + // When hidden, the default keeps source = custom_field. + ->visible(fn (Get $get): bool => count($this->options->getAvailableSourceOptions($get)) > 1) + ->columnSpan(3); + + $schema[] = Select::make('field_code') + ->label(__('custom-fields::custom-fields.visibility.field')) + ->options(fn (Get $get): array => $this->options->getAvailableFields($get)) + ->required() + ->live() + ->afterStateUpdated(fn (Get $get, Set $set) => $this->resetValuesAndOperator($get, $set)) + ->columnSpan(3); + + $schema[] = Select::make('operator') + ->label(__('custom-fields::custom-fields.visibility.operator')) + ->options(fn (Get $get): array => $this->options->getCompatibleOperators($get)) + ->required() + ->live() + ->afterStateUpdated(fn (Get $get, Set $set) => $this->clearValuesForOperatorChange($get, $set)) + ->columnSpan(2); + + $schema = [...$schema, ...$this->getValueInputComponents(4)]; + + $schema[] = Hidden::make('value')->default(null); + + return $schema; + } + + /** + * @return array + * + * @throws Exception + */ + private function getValueInputComponents(int $columnSpan = 5): array + { + return [ + Select::make('single_value') + ->label(__('custom-fields::custom-fields.visibility.value')) + ->live() + ->searchable() + ->options(fn (Get $get): array => $this->options->getFieldOptions($get)) + ->visible(fn (Get $get): bool => $this->shouldShowSingleSelect($get)) + ->placeholder(fn (Get $get): string => $this->getPlaceholder($get)) + // Scalar value inputs must ignore array values (relation/multi-choice conditions), else hydrating + // an array into a single-select throws "Array to string conversion". + ->afterStateHydrated(fn (Select $component, Get $get): Select => $component->state(is_array($get('value')) ? null : $get('value'))) + ->afterStateUpdated(fn (mixed $state, Set $set): mixed => $set('value', $state)) + ->columnSpan($columnSpan), + + Select::make('multiple_values') + ->label(__('custom-fields::custom-fields.visibility.value')) + ->live() + ->searchable() + ->multiple() + ->options(fn (Get $get): array => $this->options->getFieldOptions($get)) + ->visible(fn (Get $get): bool => $this->shouldShowMultipleSelect($get)) + ->placeholder(fn (Get $get): string => $this->getPlaceholder($get)) + ->afterStateHydrated(fn (Select $component, Get $get): Select => $component->state(value($get('value')) ? (array) $get('value') : [])) + ->afterStateUpdated(fn (array $state, Set $set): mixed => $set('value', $state)) + ->columnSpan($columnSpan), + + Toggle::make('boolean_value') + ->inline(false) + ->label(__('custom-fields::custom-fields.visibility.value')) + ->visible(fn (Get $get): bool => $this->shouldShowToggle($get)) + ->afterStateHydrated(fn (Toggle $component, Get $get): Toggle => $component->state(is_array($get('value')) ? false : $get('value'))) + ->afterStateUpdated(fn (bool $state, Set $set): mixed => $set('value', $state)) + ->columnSpan($columnSpan), + + TextInput::make('text_value') + ->label(__('custom-fields::custom-fields.visibility.value')) + ->placeholder(fn (Get $get): string => $this->getPlaceholder($get)) + ->visible(fn (Get $get): bool => $this->shouldShowTextInput($get)) + ->afterStateHydrated(fn (TextInput $component, Get $get): TextInput => $component->state(is_array($get('value')) ? '' : ($get('value') ?? ''))) + ->afterStateUpdated(fn (mixed $state, Set $set): mixed => $set('value', $state)) + ->columnSpan($columnSpan), + + Select::make('relation_values') + ->label(__('custom-fields::custom-fields.visibility.value')) + ->multiple() + ->searchable() + ->options(fn (Get $get): array => $this->options->getRelationValueOptions($get)) + ->visible(fn (Get $get): bool => $this->options->isRelationAttributeSource($get) && $this->operatorRequiresValue($get)) + ->afterStateHydrated(fn (Select $component, Get $get): Select => $component->state(value($get('value')) ? (array) $get('value') : [])) + ->afterStateUpdated(fn (mixed $state, Set $set): mixed => $set('value', $state)) + ->columnSpan($columnSpan), + ]; + } + + private function shouldShowSingleSelect(Get $get): bool + { + if (! $this->operatorRequiresValue($get)) { + return false; + } + + if ($this->options->isModelAttributeSource($get)) { + return false; + } + + if ($this->options->isRelationAttributeSource($get)) { + return false; + } + + $fieldData = $this->options->getFieldTypeData($get); + if ($fieldData === null) { + return false; + } + + if (! $fieldData->dataType->isChoiceField()) { + return false; + } + + $operator = $get('operator'); + if (! $fieldData->dataType->isMultiChoiceField()) { + return true; + } + + return ! $this->isContainsOperator($operator); + } + + private function shouldShowMultipleSelect(Get $get): bool + { + if (! $this->operatorRequiresValue($get)) { + return false; + } + + if ($this->options->isModelAttributeSource($get)) { + return false; + } + + if ($this->options->isRelationAttributeSource($get)) { + return false; + } + + $fieldData = $this->options->getFieldTypeData($get); + if ($fieldData === null) { + return false; + } + + return $fieldData->dataType->isMultiChoiceField() && + $this->isContainsOperator($get('operator')); + } + + private function shouldShowToggle(Get $get): bool + { + if (! $this->operatorRequiresValue($get)) { + return false; + } + + if ($this->options->isRelationAttributeSource($get)) { + return false; + } + + if ($this->options->isModelAttributeSource($get)) { + $entityType = $this->options->getEntityType($get); + if (blank($entityType)) { + return false; + } + + $fieldCode = $get('field_code'); + if (blank($fieldCode)) { + return false; + } + + $dataType = app(ModelAttributeDiscoveryService::class)->getAttributeDataType($entityType, $fieldCode); + + return $dataType === FieldDataType::BOOLEAN; + } + + $fieldData = $this->options->getFieldTypeData($get); + + return $fieldData && $fieldData->dataType === FieldDataType::BOOLEAN; + } + + private function shouldShowTextInput(Get $get): bool + { + if (! $this->operatorRequiresValue($get)) { + return false; + } + + if ($this->options->isRelationAttributeSource($get)) { + return false; + } + + if ($this->options->isModelAttributeSource($get)) { + return ! $this->shouldShowToggle($get); + } + + $fieldData = $this->options->getFieldTypeData($get); + if ($fieldData === null) { + return true; + } + + return ! $fieldData->dataType->isChoiceField() && + $fieldData->dataType !== FieldDataType::BOOLEAN; + } + + private function getPlaceholder(Get $get): string + { + if (blank($get('field_code'))) { + return 'Select a field first'; + } + + if (blank($get('operator'))) { + return 'Select an operator first'; + } + + if ($this->options->isModelAttributeSource($get)) { + return 'Enter comparison value'; + } + + $fieldData = $this->options->getFieldTypeData($get); + if ($fieldData === null) { + return 'Enter comparison value'; + } + + if ($fieldData->dataType->isChoiceField()) { + return $this->shouldShowMultipleSelect($get) + ? 'Select one or more options' + : 'Select an option'; + } + + return match ($fieldData->dataType) { + FieldDataType::NUMERIC => 'Enter a number', + FieldDataType::DATE, FieldDataType::DATE_TIME => 'Enter a date (YYYY-MM-DD)', + FieldDataType::BOOLEAN => 'Toggle value', + default => 'Enter comparison value', + }; + } + + private function operatorRequiresValue(Get $get): bool + { + $operator = $get('operator'); + if (blank($operator)) { + return true; + } + + return rescue( + fn () => VisibilityOperator::from($operator)->requiresValue(), + true + ); + } + + private function resetConditionValues(?Get $get, Set $set): void + { + $this->clearAllValueFields($set); + $set('field_code', null); + + if ($get instanceof Get) { + $set('operator', array_key_first($this->options->getCompatibleOperators($get))); + } + } + + private function resetValuesAndOperator(Get $get, Set $set): void + { + $this->clearAllValueFields($set); + $set('operator', array_key_first($this->options->getCompatibleOperators($get))); + } + + private function clearValuesForOperatorChange(Get $get, Set $set): void + { + // Switching between value-taking operators (e.g. Is in -> Is not in) must keep the value; + // clearing unconditionally here previously wiped a saved condition's value on any operator + // change, silently turning it into an "is in / is not in nothing" match. + if ($this->operatorRequiresValue($get)) { + return; + } + + $this->clearAllValueFields($set); + } + + private function clearAllValueFields(Set $set): void + { + $set('value', null); + $set('text_value', null); + $set('boolean_value', false); + $set('single_value', null); + $set('multiple_values', []); + $set('relation_values', []); + } + + private function isContainsOperator(?string $operator): bool + { + return in_array($operator, [ + VisibilityOperator::CONTAINS->value, + VisibilityOperator::NOT_CONTAINS->value, + ], true); + } +} diff --git a/src/Filament/Management/Forms/Components/VisibilityComponent.php b/src/Filament/Management/Forms/Components/VisibilityComponent.php index 1b3fa6a8..0eee48e6 100644 --- a/src/Filament/Management/Forms/Components/VisibilityComponent.php +++ b/src/Filament/Management/Forms/Components/VisibilityComponent.php @@ -5,54 +5,25 @@ namespace Relaticle\CustomFields\Filament\Management\Forms\Components; use Closure; -use Exception; -use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Select; -use Filament\Forms\Components\TextInput; -use Filament\Forms\Components\Toggle; use Filament\Schemas\Components\Component; use Filament\Schemas\Components\Fieldset; use Filament\Schemas\Components\Utilities\Get; -use Filament\Schemas\Components\Utilities\Set; -use Illuminate\Database\Eloquent\Model; -use Relaticle\CustomFields\CustomFields; -use Relaticle\CustomFields\Enums\ConditionSource; -use Relaticle\CustomFields\Enums\CustomFieldsFeature; -use Relaticle\CustomFields\Enums\FieldDataType; use Relaticle\CustomFields\Enums\VisibilityLogic; use Relaticle\CustomFields\Enums\VisibilityMode; -use Relaticle\CustomFields\Enums\VisibilityOperator; -use Relaticle\CustomFields\Facades\CustomFieldsType; -use Relaticle\CustomFields\FeatureSystem\FeatureManager; -use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Filament\Management\Forms\Components\Visibility\ConditionOptions; +use Relaticle\CustomFields\Filament\Management\Forms\Components\Visibility\ConditionRow; use Relaticle\CustomFields\Models\CustomFieldSection; -use Relaticle\CustomFields\Services\ModelAttributeDiscoveryService; -use Relaticle\CustomFields\Services\RelationConditionResolver; -use Relaticle\CustomFields\Services\Visibility\BackendVisibilityService; -use Relaticle\CustomFields\Support\RelationConditionConfig; final class VisibilityComponent extends Component { protected string $view = 'filament-schemas::components.grid'; - private bool $forSection = false; - - private ?string $sectionEntityType = null; - - /** - * The section the conditioned field/section belongs to. When a consumer registers a - * scope resolver, this is handed to it so the "depends on" picker can be constrained - * to a subset (e.g. only fields in the same parent form). Null applies no scope. - */ - private ?CustomFieldSection $scopeSection = null; - - /** @var ?Closure(string, ?CustomFieldSection): ?Closure */ - private static ?Closure $availableFieldsScopeResolver = null; + private ConditionOptions $conditionOptions; public function __construct() { - $this->schema([$this->buildFieldset()]); $this->columnSpanFull(); } @@ -66,23 +37,32 @@ public function __construct() */ public static function resolveAvailableFieldsScopeUsing(?Closure $resolver): void { - self::$availableFieldsScopeResolver = $resolver; + ConditionOptions::resolveAvailableFieldsScopeUsing($resolver); } public static function make(?CustomFieldSection $scopeSection = null): static { - $instance = new self; - $instance->scopeSection = $scopeSection; - - return $instance; + return self::conditionedBy(new ConditionOptions(scopeSection: $scopeSection)); } public static function makeForSection(string $entityType, ?CustomFieldSection $scopeSection = null): static + { + return self::conditionedBy(new ConditionOptions( + forSection: true, + sectionEntityType: $entityType, + scopeSection: $scopeSection, + )); + } + + /** + * The row schema reads the entity type and the scope section per render, so it can only be + * built once they are known: building it in the constructor would freeze an unscoped picker. + */ + private static function conditionedBy(ConditionOptions $options): static { $instance = new self; - $instance->forSection = true; - $instance->sectionEntityType = $entityType; - $instance->scopeSection = $scopeSection; + $instance->conditionOptions = $options; + $instance->schema([$instance->buildFieldset()]); return $instance; } @@ -112,7 +92,7 @@ private function buildFieldset(): Fieldset Repeater::make('settings.visibility.conditions') ->label(__('custom-fields::custom-fields.visibility.conditions')) - ->schema($this->buildConditionSchema()) + ->schema((new ConditionRow($this->conditionOptions))->components()) ->visible(fn (Get $get): bool => $this->modeRequiresConditions($get)) ->defaultItems(1) ->minItems(1) @@ -123,534 +103,10 @@ private function buildFieldset(): Fieldset ]); } - /** - * @return array - * - * @throws Exception - */ - private function buildConditionSchema(): array - { - $schema = []; - - $schema[] = Select::make('source') - ->label(__('custom-fields::custom-fields.visibility.source')) - ->options(fn (Get $get): array => $this->getAvailableSourceOptions($get)) - ->default(ConditionSource::CustomField->value) - ->required() - ->live() - ->afterStateUpdated(fn (Set $set) => $this->resetConditionValues(null, $set)) - // Show the source picker only when more than the default CustomField source is available - // (model-attribute flag on, or the entity has configured relation paths). Decided per-render - // via $get so it works in Livewire action contexts where the entity is not known at build time. - // When hidden, the default keeps source = custom_field. - ->visible(fn (Get $get): bool => count($this->getAvailableSourceOptions($get)) > 1) - ->columnSpan(3); - - $schema[] = Select::make('field_code') - ->label(__('custom-fields::custom-fields.visibility.field')) - ->options(fn (Get $get): array => $this->getAvailableFields($get)) - ->required() - ->live() - ->afterStateUpdated(fn (Get $get, Set $set) => $this->resetValuesAndOperator($get, $set)) - ->columnSpan(3); - - $schema[] = Select::make('operator') - ->label(__('custom-fields::custom-fields.visibility.operator')) - ->options(fn (Get $get): array => $this->getCompatibleOperators($get)) - ->required() - ->live() - ->afterStateUpdated(fn (Get $get, Set $set) => $this->clearValuesForOperatorChange($get, $set)) - ->columnSpan(2); - - $schema = [...$schema, ...$this->getValueInputComponents(4)]; - - $schema[] = Hidden::make('value')->default(null); - - return $schema; - } - - /** - * @return array - * - * @throws Exception - */ - private function getValueInputComponents(int $columnSpan = 5): array - { - return [ - Select::make('single_value') - ->label(__('custom-fields::custom-fields.visibility.value')) - ->live() - ->searchable() - ->options(fn (Get $get): array => $this->getFieldOptions($get)) - ->visible(fn (Get $get): bool => $this->shouldShowSingleSelect($get)) - ->placeholder(fn (Get $get): string => $this->getPlaceholder($get)) - // Scalar value inputs must ignore array values (relation/multi-choice conditions), else hydrating - // an array into a single-select throws "Array to string conversion". - ->afterStateHydrated(fn (Select $component, Get $get): Select => $component->state(is_array($get('value')) ? null : $get('value'))) - ->afterStateUpdated(fn (mixed $state, Set $set): mixed => $set('value', $state)) - ->columnSpan($columnSpan), - - Select::make('multiple_values') - ->label(__('custom-fields::custom-fields.visibility.value')) - ->live() - ->searchable() - ->multiple() - ->options(fn (Get $get): array => $this->getFieldOptions($get)) - ->visible(fn (Get $get): bool => $this->shouldShowMultipleSelect($get)) - ->placeholder(fn (Get $get): string => $this->getPlaceholder($get)) - ->afterStateHydrated(fn (Select $component, Get $get): Select => $component->state(value($get('value')) ? (array) $get('value') : [])) - ->afterStateUpdated(fn (array $state, Set $set): mixed => $set('value', $state)) - ->columnSpan($columnSpan), - - Toggle::make('boolean_value') - ->inline(false) - ->label(__('custom-fields::custom-fields.visibility.value')) - ->visible(fn (Get $get): bool => $this->shouldShowToggle($get)) - ->afterStateHydrated(fn (Toggle $component, Get $get): Toggle => $component->state(is_array($get('value')) ? false : $get('value'))) - ->afterStateUpdated(fn (bool $state, Set $set): mixed => $set('value', $state)) - ->columnSpan($columnSpan), - - TextInput::make('text_value') - ->label(__('custom-fields::custom-fields.visibility.value')) - ->placeholder(fn (Get $get): string => $this->getPlaceholder($get)) - ->visible(fn (Get $get): bool => $this->shouldShowTextInput($get)) - ->afterStateHydrated(fn (TextInput $component, Get $get): TextInput => $component->state(is_array($get('value')) ? '' : ($get('value') ?? ''))) - ->afterStateUpdated(fn (mixed $state, Set $set): mixed => $set('value', $state)) - ->columnSpan($columnSpan), - - Select::make('relation_values') - ->label(__('custom-fields::custom-fields.visibility.value')) - ->multiple() - ->searchable() - ->options(fn (Get $get): array => $this->getRelationValueOptions($get)) - ->visible(fn (Get $get): bool => $this->isRelationAttributeSource($get) && $this->operatorRequiresValue($get)) - ->afterStateHydrated(fn (Select $component, Get $get): Select => $component->state(value($get('value')) ? (array) $get('value') : [])) - ->afterStateUpdated(fn (mixed $state, Set $set): mixed => $set('value', $state)) - ->columnSpan($columnSpan), - ]; - } - - /** - * @return array - */ - private function getAvailableSourceOptions(Get $get): array - { - $entityType = $this->getEntityType($get); - - $options = [ - ConditionSource::CustomField->value => ConditionSource::CustomField->getLabel(), - ]; - - if (FeatureManager::isEnabled(CustomFieldsFeature::MODEL_ATTRIBUTE_CONDITIONS)) { - $options[ConditionSource::ModelAttribute->value] = ConditionSource::ModelAttribute->getLabel(); - } - - if (! blank($entityType) && app(RelationConditionConfig::class)->isRelationSourceAvailable($entityType)) { - $options[ConditionSource::RelationAttribute->value] = ConditionSource::RelationAttribute->getLabel(); - } - - return $options; - } - - private function sourceIs(Get $get, ConditionSource $expected): bool - { - $source = $get('source'); - - if ($source instanceof ConditionSource) { - return $source === $expected; - } - - return $source === $expected->value; - } - - private function isModelAttributeSource(Get $get): bool - { - return $this->sourceIs($get, ConditionSource::ModelAttribute); - } - - private function isRelationAttributeSource(Get $get): bool - { - return $this->sourceIs($get, ConditionSource::RelationAttribute); - } - - private function shouldShowSingleSelect(Get $get): bool - { - if (! $this->operatorRequiresValue($get)) { - return false; - } - - if ($this->isModelAttributeSource($get)) { - return false; - } - - if ($this->isRelationAttributeSource($get)) { - return false; - } - - $fieldData = $this->getFieldTypeData($get); - if ($fieldData === null) { - return false; - } - - if (! $fieldData->dataType->isChoiceField()) { - return false; - } - - $operator = $get('operator'); - if (! $fieldData->dataType->isMultiChoiceField()) { - return true; - } - - return ! $this->isContainsOperator($operator); - } - - private function shouldShowMultipleSelect(Get $get): bool - { - if (! $this->operatorRequiresValue($get)) { - return false; - } - - if ($this->isModelAttributeSource($get)) { - return false; - } - - if ($this->isRelationAttributeSource($get)) { - return false; - } - - $fieldData = $this->getFieldTypeData($get); - if ($fieldData === null) { - return false; - } - - return $fieldData->dataType->isMultiChoiceField() && - $this->isContainsOperator($get('operator')); - } - - private function shouldShowToggle(Get $get): bool - { - if (! $this->operatorRequiresValue($get)) { - return false; - } - - if ($this->isRelationAttributeSource($get)) { - return false; - } - - if ($this->isModelAttributeSource($get)) { - $entityType = $this->getEntityType($get); - if (blank($entityType)) { - return false; - } - - $fieldCode = $get('field_code'); - if (blank($fieldCode)) { - return false; - } - - $dataType = app(ModelAttributeDiscoveryService::class)->getAttributeDataType($entityType, $fieldCode); - - return $dataType === FieldDataType::BOOLEAN; - } - - $fieldData = $this->getFieldTypeData($get); - - return $fieldData && $fieldData->dataType === FieldDataType::BOOLEAN; - } - - private function shouldShowTextInput(Get $get): bool - { - if (! $this->operatorRequiresValue($get)) { - return false; - } - - if ($this->isRelationAttributeSource($get)) { - return false; - } - - if ($this->isModelAttributeSource($get)) { - return ! $this->shouldShowToggle($get); - } - - $fieldData = $this->getFieldTypeData($get); - if ($fieldData === null) { - return true; - } - - return ! $fieldData->dataType->isChoiceField() && - $fieldData->dataType !== FieldDataType::BOOLEAN; - } - - /** - * @return array - */ - private function getFieldOptions(Get $get): array - { - if ($this->isModelAttributeSource($get)) { - return []; - } - - $fieldCode = $get('field_code'); - if (blank($fieldCode)) { - return []; - } - - $entityType = $this->getEntityType($get); - if (blank($entityType)) { - return []; - } - - return rescue(function () use ($fieldCode, $entityType) { - return app(BackendVisibilityService::class) - ->getFieldOptions($fieldCode, $entityType); - }, []); - } - - private function getPlaceholder(Get $get): string - { - if (blank($get('field_code'))) { - return 'Select a field first'; - } - - if (blank($get('operator'))) { - return 'Select an operator first'; - } - - if ($this->isModelAttributeSource($get)) { - return 'Enter comparison value'; - } - - $fieldData = $this->getFieldTypeData($get); - if ($fieldData === null) { - return 'Enter comparison value'; - } - - if ($fieldData->dataType->isChoiceField()) { - return $this->shouldShowMultipleSelect($get) - ? 'Select one or more options' - : 'Select an option'; - } - - return match ($fieldData->dataType) { - FieldDataType::NUMERIC => 'Enter a number', - FieldDataType::DATE, FieldDataType::DATE_TIME => 'Enter a date (YYYY-MM-DD)', - FieldDataType::BOOLEAN => 'Toggle value', - default => 'Enter comparison value', - }; - } - private function modeRequiresConditions(Get $get): bool { $mode = $get('settings.visibility.mode'); return $mode instanceof VisibilityMode && $mode->requiresConditions(); } - - private function operatorRequiresValue(Get $get): bool - { - $operator = $get('operator'); - if (blank($operator)) { - return true; - } - - return rescue( - fn () => VisibilityOperator::from($operator)->requiresValue(), - true - ); - } - - /** - * @return array - */ - private function getAvailableFields(Get $get): array - { - $entityType = $this->getEntityType($get); - if (blank($entityType)) { - return []; - } - - if ($this->isRelationAttributeSource($get)) { - return app(RelationConditionConfig::class)->relationsFor($entityType); - } - - if ($this->isModelAttributeSource($get)) { - return rescue( - fn (): array => app(ModelAttributeDiscoveryService::class)->getAttributeOptions($entityType), - [] - ); - } - - $currentFieldCode = $this->forSection ? null : $get('../../../../code'); - $scopeSection = $this->scopeSection; - $scopeResolver = self::$availableFieldsScopeResolver; - - return rescue(function () use ($entityType, $currentFieldCode, $scopeSection, $scopeResolver) { - $query = CustomFields::customFieldModel()::query() - ->forMorphEntity($entityType) - ->when($currentFieldCode, fn (mixed $query) => $query->where('code', '!=', $currentFieldCode)); - - if ($scopeResolver instanceof Closure) { - $constraint = $scopeResolver($entityType, $scopeSection); - - if ($constraint instanceof Closure) { - $query = $constraint($query) ?? $query; - } - } - - return $query->orderBy('name') - ->pluck('name', 'code') - ->toArray(); - }, []); - } - - /** - * @return array - */ - private function getCompatibleOperators(Get $get): array - { - if ($this->isRelationAttributeSource($get)) { - return [ - VisibilityOperator::IS_IN->value => VisibilityOperator::IS_IN->getLabel(), - VisibilityOperator::IS_NOT_IN->value => VisibilityOperator::IS_NOT_IN->getLabel(), - ]; - } - - if ($this->isModelAttributeSource($get)) { - return collect(VisibilityOperator::options()) - ->except([VisibilityOperator::IS_IN->value, VisibilityOperator::IS_NOT_IN->value]) - ->all(); - } - - $fieldData = $this->getFieldTypeData($get); - - return $fieldData - ? $fieldData->getCompatibleOperatorOptions() - : collect(VisibilityOperator::options()) - ->except([VisibilityOperator::IS_IN->value, VisibilityOperator::IS_NOT_IN->value]) - ->all(); - } - - /** - * @return array - */ - private function getRelationValueOptions(Get $get): array - { - $path = $get('field_code'); - - if (blank($path)) { - return []; - } - - $entityType = $this->getEntityType($get); - if (blank($entityType)) { - return []; - } - - $related = app(RelationConditionResolver::class)->resolveTerminalRelatedModel($entityType, (string) $path); - - if (! $related instanceof Model) { - return []; - } - - static $labelColumns = []; - $modelClass = $related::class; - if (! isset($labelColumns[$modelClass])) { - $labelColumns[$modelClass] = collect(['name', 'title', 'label']) - ->first(fn (string $column): bool => $related->getConnection()->getSchemaBuilder()->hasColumn($related->getTable(), $column)) - ?? $related->getKeyName(); - } - - $labelColumn = $labelColumns[$modelClass]; - - return $related::query()->pluck($labelColumn, $related->getKeyName())->all(); - } - - private function getFieldTypeData(Get $get): ?object - { - $fieldCode = $get('field_code'); - if (blank($fieldCode)) { - return null; - } - - $field = $this->getCustomField($fieldCode, $get); - if (! $field instanceof CustomField) { - return null; - } - - return rescue( - fn () => CustomFieldsType::getFieldType($field->type) - ); - } - - private function getCustomField(string $fieldCode, Get $get): ?CustomField - { - $entityType = $this->getEntityType($get); - if (blank($entityType)) { - return null; - } - - return rescue(function () use ($entityType, $fieldCode) { - return CustomFields::customFieldModel()::query() - ->forMorphEntity($entityType) - ->where('code', $fieldCode) - ->first(); - }); - } - - private function getEntityType(?Get $get = null): ?string - { - if ($this->forSection && $this->sectionEntityType) { - return $this->sectionEntityType; - } - - return ($get instanceof Get ? $get('../../../../entity_type') : null) - ?? request('entityType') - ?? request()->route('entityType'); - } - - private function resetConditionValues(?Get $get, Set $set): void - { - $this->clearAllValueFields($set); - $set('field_code', null); - - if ($get instanceof Get) { - $set('operator', array_key_first($this->getCompatibleOperators($get))); - } - } - - private function resetValuesAndOperator(Get $get, Set $set): void - { - $this->clearAllValueFields($set); - $set('operator', array_key_first($this->getCompatibleOperators($get))); - } - - private function clearValuesForOperatorChange(Get $get, Set $set): void - { - // Switching between value-taking operators (e.g. Is in -> Is not in) must keep the value; - // clearing unconditionally here previously wiped a saved condition's value on any operator - // change, silently turning it into an "is in / is not in nothing" match. - if ($this->operatorRequiresValue($get)) { - return; - } - - $this->clearAllValueFields($set); - } - - private function clearAllValueFields(Set $set): void - { - $set('value', null); - $set('text_value', null); - $set('boolean_value', false); - $set('single_value', null); - $set('multiple_values', []); - $set('relation_values', []); - } - - private function isContainsOperator(?string $operator): bool - { - return in_array($operator, [ - VisibilityOperator::CONTAINS->value, - VisibilityOperator::NOT_CONTAINS->value, - ], true); - } } diff --git a/src/Filament/Management/Pages/CustomFieldsManagementPage.php b/src/Filament/Management/Pages/CustomFieldsManagementPage.php index 125310cc..6f45c7ed 100644 --- a/src/Filament/Management/Pages/CustomFieldsManagementPage.php +++ b/src/Filament/Management/Pages/CustomFieldsManagementPage.php @@ -18,12 +18,12 @@ use Override; use Relaticle\CustomFields\CustomFields as CustomFieldsModel; use Relaticle\CustomFields\CustomFieldsPlugin; -use Relaticle\CustomFields\Data\EntityConfigurationData; use Relaticle\CustomFields\Enums\CustomFieldSectionType; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\Facades\Entities; use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Filament\Management\Schemas\SectionForm; +use Relaticle\CustomFields\Models\CustomField; use Relaticle\CustomFields\Models\CustomFieldSection; use Relaticle\CustomFields\Services\TenantContextService; use Relaticle\CustomFields\Support\CodeGenerator; @@ -59,6 +59,9 @@ public function isSectionsDisabled(): bool return ! FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS); } + /** + * @return Collection + */ #[Computed] public function sections(): Collection { @@ -67,19 +70,22 @@ public function sections(): Collection return collect(); } - return CustomFieldsModel::newSectionModel()->query() - ->withDeactivated() + return CustomFieldsModel::newSectionModel()::withDeactivated() ->forEntityType($this->currentEntityType) - ->with([ - 'fields' => function (HasMany $query): void { - $query->forMorphEntity($this->currentEntityType) - ->orderBy('sort_order'); - }, - ]) + ->with(['fields' => $this->orderFieldsOfCurrentEntity(...)]) ->orderBy('sort_order') ->get(); } + /** + * @param HasMany $query + */ + private function orderFieldsOfCurrentEntity(HasMany $query): void + { + $query->forMorphEntity($this->currentEntityType) + ->orderBy('sort_order'); + } + #[Computed] public function currentEntityLabel(): string { @@ -104,14 +110,13 @@ public function currentEntityIcon(): string return $entity?->getIcon() ?? 'heroicon-o-document'; } + /** + * @return Collection + */ #[Computed] public function entityTypes(): Collection { - return Entities::globallyManaged() - ->sortedByPriority() - ->mapWithKeys(fn (EntityConfigurationData $entity): array => [ - $entity->getAlias() => $entity->getLabelPlural(), - ]); + return collect(Entities::globallyManaged()->sortedByPriority()->toOptions()); } /** @@ -166,8 +171,7 @@ public function updateSectionsOrder(array $sections): void $sectionModel = CustomFieldsModel::newSectionModel(); foreach ($sections as $index => $section) { - $sectionModel->query() - ->withDeactivated() + $sectionModel::withDeactivated() ->where($sectionModel->getKeyName(), $section) ->update([ 'sort_order' => $index, @@ -175,6 +179,9 @@ public function updateSectionsOrder(array $sections): void } } + /** + * @param array $data + */ private function storeSection(array $data): CustomFieldSection { if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { @@ -198,7 +205,7 @@ private function storeSection(array $data): CustomFieldSection #[On('section-deleted')] public function sectionDeleted(): void { - $this->sections = $this->sections->filter(fn (CustomFieldSection $section): bool => $section->exists); + unset($this->sections); } #[Override] diff --git a/src/Filament/Management/Schemas/FieldForm.php b/src/Filament/Management/Schemas/FieldForm.php index 4e18fabc..42b73b6a 100644 --- a/src/Filament/Management/Schemas/FieldForm.php +++ b/src/Filament/Management/Schemas/FieldForm.php @@ -5,6 +5,8 @@ namespace Relaticle\CustomFields\Filament\Management\Schemas; use Closure; +use Filament\Actions\Action; +use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\ColorPicker; use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Repeater; @@ -13,31 +15,45 @@ use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; +use Filament\Notifications\Notification; use Filament\Schemas\Components\Component; use Filament\Schemas\Components\Fieldset; use Filament\Schemas\Components\Grid; +use Filament\Schemas\Components\Group; +use Filament\Schemas\Components\Section; use Filament\Schemas\Components\Tabs; use Filament\Schemas\Components\Tabs\Tab; use Filament\Schemas\Components\Utilities\Get; use Filament\Schemas\Components\Utilities\Set; +use Filament\Support\Enums\Width; use Filament\Support\Icons\Heroicon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Arr; use Illuminate\Support\Str; use Illuminate\Validation\Rules\Unique; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; use Relaticle\CustomFields\CustomFields; +use Relaticle\CustomFields\Data\CustomFieldOptionSettingsData; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\Enums\DescriptionPosition; +use Relaticle\CustomFields\Enums\OptionCategory; +use Relaticle\CustomFields\Enums\RelationshipCardinality; +use Relaticle\CustomFields\Enums\UiSurface; use Relaticle\CustomFields\Facades\CustomFieldsType; use Relaticle\CustomFields\Facades\Entities; use Relaticle\CustomFields\FeatureSystem\FeatureManager; +use Relaticle\CustomFields\FieldTypeSystem\Definitions\StatusFieldType; +use Relaticle\CustomFields\Filament\Management\Forms\Components\RelationshipConfigurator; use Relaticle\CustomFields\Filament\Management\Forms\Components\TypeField; use Relaticle\CustomFields\Filament\Management\Forms\Components\VisibilityComponent; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldRelationship; use Relaticle\CustomFields\Models\CustomFieldSection; use Relaticle\CustomFields\Services\TenantContextService; +use Relaticle\CustomFields\Support\OptionNameParser; +use Relaticle\CustomFields\Support\ViewFlavor; -class FieldForm implements FormInterface +final class FieldForm implements FormInterface { /** @var ?Closure(?CustomFieldSection): ?Closure */ private static ?Closure $uniqueNameRuleModifierResolver = null; @@ -93,6 +109,464 @@ private static function resolveUniqueCodeRuleModifier(?CustomFieldSection $secti return null; } + /** + * Both link types configure a relationship definition, and they ask for different things: + * a record field points one way, so it asks where and how many; a relationship field owns + * both ends, so it gets the configurator. Only one frame is ever visible. + */ + private static function recordConfiguration(): Group + { + return Group::make() + ->columnSpanFull() + ->schema([ + self::oneWayConfiguration(), + self::pairedConfiguration(), + ]); + } + + /** + * The record type's configuration, unchanged since 3.x: the entity it links to, locked + * once the field exists, and whether it holds more than one record. Cardinality carries + * the answer, so the toggle is what the user reads and the definition is what it writes. + */ + private static function oneWayConfiguration(): Fieldset + { + return Fieldset::make(__('custom-fields::custom-fields.field.form.record.label')) + ->columns(2) + ->columnSpanFull() + ->visible(fn (Get $get): bool => self::isOneWayRecordField($get('type'))) + ->schema([ + self::targetEntitySelect(), + Toggle::make('relationship.allow_multiple') + ->inline() + ->live() + ->label(__('custom-fields::custom-fields.field.form.allow_multiple')) + ->hintIcon(Heroicon::OutlinedQuestionMarkCircle, tooltip: __('custom-fields::custom-fields.field.form.allow_multiple_help')) + ->default(false), + // The toggle answers for this field's own end, so the cardinality it asks + // for keeps whatever the other end already holds. + self::keepFirstConfirmation(function (Get $get, ?CustomField $record): ?RelationshipCardinality { + $definition = $record?->relationshipDefinition(); + + if (! $record instanceof CustomField || ! $definition instanceof CustomFieldRelationship) { + return null; + } + + return $definition->orientCardinality($record, $definition->cardinality) + ->fromSideHolds($get('relationship.allow_multiple') === true); + }), + ]); + } + + /** + * The relationship type's configuration: where the field points, how many records each + * end holds, and the field rendering the other end. + */ + private static function pairedConfiguration(): Component + { + $view = ViewFlavor::view(UiSurface::RelationshipConfigurator); + $components = self::recordConfigurationComponents(); + + // The flavor decides the frame the same children are placed in, and nothing else: + // every closure below is shared, so the two presentations cannot drift apart. + if ($view === null) { + return Fieldset::make(__('custom-fields::custom-fields.field.form.record.label')) + ->columns(2) + ->columnSpanFull() + ->visible(fn (Get $get): bool => self::isPairedField($get('type'))) + ->schema($components); + } + + return RelationshipConfigurator::make() + ->view($view) + ->columnSpanFull() + ->visible(fn (Get $get): bool => self::isPairedField($get('type'))) + ->schema($components); + } + + /** + * The machine code is derived from the name and rarely touched by hand, so it sits behind + * a disclosure instead of beside the name it comes from. Uniqueness is scoped per entity + * type, and per tenant when the host is multi-tenant. + */ + private static function advancedDisclosure(?Closure $uniqueCodeRuleModifier): Section + { + return Section::make(__('custom-fields::custom-fields.field.form.advanced')) + ->description(__('custom-fields::custom-fields.field.form.advanced_description')) + ->icon(Heroicon::OutlinedWrenchScrewdriver) + ->collapsible() + ->collapsed() + ->columnSpanFull() + ->visible(fn (): bool => ! FeatureManager::isEnabled(CustomFieldsFeature::FIELD_CODE_AUTO_GENERATE)) + ->schema([ + TextInput::make('code') + ->label(__('custom-fields::custom-fields.field.form.code')) + ->live(onBlur: true) + ->required(fn (): bool => ! FeatureManager::isEnabled(CustomFieldsFeature::FIELD_CODE_AUTO_GENERATE)) + ->alphaDash() + ->maxLength(50) + ->disabled(self::disabledForSystemFields()) + ->visible(fn (): bool => ! FeatureManager::isEnabled(CustomFieldsFeature::FIELD_CODE_AUTO_GENERATE)) + ->unique( + table: CustomFields::customFieldModel(), + column: 'code', + ignoreRecord: true, + modifyRuleUsing: function (Unique $rule, Get $get) use ($uniqueCodeRuleModifier): Unique { + $rule = $rule + ->where('entity_type', $get('entity_type')) + ->when( + FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY), + fn (Unique $rule) => $rule->where( + config('custom-fields.database.column_names.tenant_foreign_key'), + TenantContextService::getCurrentTenantId() + ) + ); + + if ($uniqueCodeRuleModifier instanceof Closure) { + return $uniqueCodeRuleModifier($rule, $get); + } + + return $rule; + } + ) + ->afterStateUpdated(function (Set $set, ?string $state): void { + $set('code', Str::of($state)->slug('_')->toString()); + }), + ]); + } + + /** + * A vocabulary is pasted, not clicked in one option at a time. The rows are appended + * through the repeater's own state so tenant stamping and sort_order keep working, which + * is why nothing here writes an option model. + */ + private static function pasteOptionsAction(): Action + { + return Action::make('pasteOptions') + ->label(__('custom-fields::custom-fields.field.form.options.paste')) + ->icon(Heroicon::OutlinedClipboardDocumentList) + ->link() + ->modalHeading(__('custom-fields::custom-fields.field.form.options.paste_modal_heading')) + ->modalSubmitActionLabel(__('custom-fields::custom-fields.field.form.options.paste_submit')) + ->modalWidth(Width::Large) + ->schema([ + Textarea::make('names') + ->label(__('custom-fields::custom-fields.field.form.options.paste_names')) + ->helperText(__('custom-fields::custom-fields.field.form.options.paste_names_help', [ + 'max' => OptionNameParser::MAX_NAMES, + ])) + ->rows(10) + ->required(), + ]) + ->action(function (array $data, Repeater $component): void { + $items = self::optionItems($component); + + $parsed = OptionNameParser::parse( + is_string($data['names'] ?? null) ? $data['names'] : null, + array_map(fn (array $item): mixed => $item['name'] ?? null, $items), + ); + + self::appendOptionNames($component, $parsed['names']); + + $body = __('custom-fields::custom-fields.field.form.options.pasted', [ + 'added' => count($parsed['names']), + 'duplicates' => $parsed['duplicates'], + ]); + + if ($parsed['truncated']) { + $body .= '. '.__('custom-fields::custom-fields.field.form.options.pasted_capped', [ + 'max' => OptionNameParser::MAX_NAMES, + ]); + } + + Notification::make() + ->success() + ->title(__('custom-fields::custom-fields.field.form.options.paste_modal_heading')) + ->body($body) + ->send(); + }); + } + + /** + * @return array> + */ + private static function optionItems(Repeater $component): array + { + $items = []; + + foreach (Arr::wrap($component->getRawState()) as $key => $item) { + $items[$key] = is_array($item) ? $item : []; + } + + return $items; + } + + /** + * Mirrors the repeater's own add action: a key per row, then the child schema fills it. + * + * @param list $names + */ + private static function appendOptionNames(Repeater $component, array $names): void + { + if ($names === []) { + return; + } + + // A row opened and left blank fails the required name rule, and a pasted list has no + // use for it. + $items = array_filter( + self::optionItems($component), + fn (array $item): bool => filled($item['name'] ?? null), + ); + + $filled = []; + + foreach ($names as $name) { + $uuid = $component->generateUuid(); + + if ($uuid === null) { + $items[] = []; + $filled[array_key_last($items)] = $name; + + continue; + } + + $items[$uuid] = []; + $filled[$uuid] = $name; + } + + $component->rawState($items); + + foreach ($filled as $key => $name) { + $component->getChildSchema((string) $key)?->fill(['name' => $name]); + } + + $component->callAfterStateUpdated(); + } + + /** + * The entity the field links to. Both ends of a definition are locked once it exists, so + * the select is read-only from the first save on. + */ + private static function targetEntitySelect(): Select + { + return Select::make('relationship.target_entity_type') + ->label(__('custom-fields::custom-fields.field.form.record.target')) + ->hintIcon(Heroicon::OutlinedQuestionMarkCircle, tooltip: __('custom-fields::custom-fields.field.form.record.target_help')) + ->options(Entities::getLookupOptions()) + ->default((Entities::asLookupSources()->first()?->getAlias()) ?? '') + ->disabled(fn (?CustomField $record): bool => (bool) $record?->exists) + ->required() + ->live(); + } + + /** + * A field that stops holding many records closes the edges that no longer fit, so the + * narrowing is confirmed before it is saved. Each face reads the cardinality off its own + * control, which is a toggle on one and a select on the other. + * + * @param Closure(Get, ?CustomField): ?RelationshipCardinality $cardinality + */ + private static function keepFirstConfirmation(Closure $cardinality): Checkbox + { + return Checkbox::make('relationship.keep_first') + ->label(__('custom-fields::custom-fields.field.form.record.keep_first')) + ->helperText(__('custom-fields::custom-fields.field.form.record.keep_first_help')) + ->columnSpanFull() + ->accepted() + ->default(false) + ->visible(fn (Get $get, ?CustomField $record): bool => self::narrowsCardinality($record, $cardinality($get, $record))); + } + + /** + * @return array + */ + private static function recordConfigurationComponents(): array + { + return [ + self::targetEntitySelect(), + Select::make('relationship.cardinality') + ->label(__('custom-fields::custom-fields.field.form.record.cardinality')) + ->hintIcon(Heroicon::OutlinedQuestionMarkCircle, tooltip: __('custom-fields::custom-fields.field.form.record.cardinality_help')) + ->options(fn (Get $get): array => self::cardinalityOptions($get('relationship.is_symmetric') === true)) + ->default(RelationshipCardinality::ManyToOne->value) + ->required() + ->live(), + Toggle::make('relationship.is_symmetric') + ->inline() + ->live() + ->label(__('custom-fields::custom-fields.field.form.record.is_symmetric')) + ->hintIcon(Heroicon::OutlinedQuestionMarkCircle, tooltip: __('custom-fields::custom-fields.field.form.record.is_symmetric_help')) + ->visible(fn (Get $get, ?CustomField $record): bool => $record?->exists !== true + && self::endsMatch($get('entity_type'), $get('relationship.target_entity_type'))) + ->default(false), + TextInput::make('relationship.paired_field_name') + ->label(__('custom-fields::custom-fields.field.form.record.paired_field_name')) + ->hintIcon(Heroicon::OutlinedQuestionMarkCircle, tooltip: __('custom-fields::custom-fields.field.form.record.paired_field_name_help')) + // A suggestion, never a value: filling it would turn every one-way field + // into a paired one without the user asking for a second field. + ->placeholder(fn (Get $get): ?string => self::pairedFieldNameSuggestion($get('entity_type'))) + ->maxLength(50) + ->live(onBlur: true) + ->disabled(fn (?CustomField $record): bool => (bool) $record?->exists) + ->visible(fn (Get $get, ?CustomField $record): bool => $record?->exists === true + ? filled($get('relationship.paired_field_name')) + : $get('relationship.is_symmetric') !== true), + Select::make('relationship.paired_section_id') + ->label(__('custom-fields::custom-fields.field.form.record.paired_section')) + ->hintIcon(Heroicon::OutlinedQuestionMarkCircle, tooltip: __('custom-fields::custom-fields.field.form.record.paired_section_help')) + ->options(fn (Get $get): array => self::sectionOptions($get('relationship.target_entity_type'))) + ->required() + // An entity with no section has nothing to choose, and the definition + // service puts the paired field in a default one, so asking would only + // block the save. + ->visible(fn (Get $get, ?CustomField $record): bool => FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS) + && $record?->exists !== true + && filled($get('relationship.paired_field_name')) + && $get('relationship.is_symmetric') !== true + && self::sectionOptions($get('relationship.target_entity_type')) !== []), + self::keepFirstConfirmation(fn (Get $get, ?CustomField $record): ?RelationshipCardinality => RelationshipCardinality::tryFrom((string) $get('relationship.cardinality'))), + ]; + } + + private static function pairedFieldNameSuggestion(mixed $entityType): ?string + { + if (! is_string($entityType) || $entityType === '') { + return null; + } + + return Entities::getEntity($entityType)?->getLabelPlural(); + } + + /** + * The state the record configuration is filled from: an existing field reads its own + * definition, from whichever end it renders. + * + * @return array|null + */ + public static function relationshipState(CustomField $field): ?array + { + $definition = $field->relationshipDefinition(); + + if (! $definition instanceof CustomFieldRelationship) { + return null; + } + + $partner = match (true) { + $definition->is_symmetric => null, + $definition->directionFor($field) === CustomFieldRelationship::DIRECTION_FROM => $definition->toField, + default => $definition->fromField, + }; + + return [ + 'target_entity_type' => $field->targetEntityType(), + 'cardinality' => $definition->orientCardinality($field, $definition->cardinality)->value, + 'allow_multiple' => $field->allowsMultipleRecords(), + 'is_symmetric' => $definition->is_symmetric, + 'paired_field_name' => $partner?->name, + ]; + } + + private static function isRelationshipField(mixed $type): bool + { + if (! is_string($type) || $type === '') { + return false; + } + + return CustomFieldsType::getFieldType($type)?->requiresRelationship === true; + } + + /** + * The one-way record type: it links records like the paired type does, and configures + * neither a second field nor a cardinality of its own. + */ + private static function isOneWayRecordField(mixed $type): bool + { + return self::isRelationshipField($type) && ! self::isPairedField($type); + } + + private static function isPairedField(mixed $type): bool + { + if (! is_string($type) || $type === '') { + return false; + } + + return CustomFieldsType::getFieldType($type)?->supportsPairing === true; + } + + /** + * A symmetric relationship reads one field from both ends, so a cardinality that + * constrains only one of them cannot describe it. + * + * @return array + */ + private static function cardinalityOptions(bool $isSymmetric): array + { + $cases = $isSymmetric + ? [RelationshipCardinality::OneToOne, RelationshipCardinality::ManyToMany] + : RelationshipCardinality::cases(); + + $options = []; + + foreach ($cases as $case) { + $options[$case->value] = $case->getLabel(); + } + + return $options; + } + + private static function endsMatch(mixed $entityType, mixed $targetEntityType): bool + { + if (! is_string($entityType) || ! is_string($targetEntityType) || $entityType === '' || $targetEntityType === '') { + return false; + } + + $alias = Entities::getEntity($entityType)?->getAlias(); + + // Two entity types the host has not registered are not the same entity, and a + // relationship cannot be symmetric across an end that resolves to nothing. + return $alias !== null && $alias === Entities::getEntity($targetEntityType)?->getAlias(); + } + + /** + * The sections of the entity the paired field lands on. Sections are what the activable + * scope reads, so a paired field without one would never render. + * + * @return array + */ + private static function sectionOptions(mixed $entityType): array + { + if (! is_string($entityType) || $entityType === '') { + return []; + } + + $entity = Entities::getEntity($entityType); + $candidates = array_values(array_unique(array_filter([$entityType, $entity?->getAlias(), $entity?->getModelClass()]))); + + $options = []; + + foreach (CustomFields::newSectionModel()->newQuery()->whereIn('entity_type', $candidates)->orderBy('sort_order')->get() as $section) { + $options[(string) $section->getKey()] = (string) $section->name; + } + + return $options; + } + + private static function narrowsCardinality(?CustomField $record, ?RelationshipCardinality $target): bool + { + if (! $record instanceof CustomField || ! $record->exists || ! $target instanceof RelationshipCardinality) { + return false; + } + + $definition = $record->relationshipDefinition(); + + if (! $definition instanceof CustomFieldRelationship) { + return false; + } + + return $definition->orientCardinality($record, $definition->cardinality)->narrows($target); + } + /** * Disable field when editing a system-defined custom field. */ @@ -101,6 +575,17 @@ private static function disabledForSystemFields(): Closure return fn (?CustomField $record): bool => $record?->isSystemDefined() ?? false; } + // The options repeater pairs each table column with the schema component in the same + // position, so the category header and the category select answer one question. + private static function showsOptionCategories(mixed $type): bool + { + if (! is_string($type) || $type === '') { + return false; + } + + return CustomFieldsType::getFieldType($type)?->carriesOptionCategories === true; + } + /** * Get type-specific settings schema components. * @@ -144,7 +629,7 @@ private static function getValidationSchema(): array } foreach ($fieldTypeData->validationCapabilities as $capabilityClass) { - /** @var ValidationCapability $capability */ + /** @var ValidationCapabilityInterface $capability */ $capability = app($capabilityClass); $capabilityComponents = $capability->formSchema('validation_rules'); @@ -160,17 +645,25 @@ private static function getValidationSchema(): array } /** + * A create action that fills the form hydrates that state instead of the schema's own + * defaults, so the entity type arrives here rather than through fillForm(). + * * @return array */ - public static function schema(bool $withOptionsRelationship = true, ?CustomFieldSection $section = null): array + public static function schema(bool $withOptionsRelationship = true, ?CustomFieldSection $section = null, ?string $entityType = null): array { $uniqueNameRuleModifier = self::resolveUniqueNameRuleModifier($section); $uniqueCodeRuleModifier = self::resolveUniqueCodeRuleModifier($section); $optionsRepeater = Repeater::make('options') - ->table([ + ->table(fn (Get $get): array => [ TableColumn::make('Color')->width('150px')->hiddenHeaderLabel(), TableColumn::make('Name')->hiddenHeaderLabel(), + ...(self::showsOptionCategories($get('type')) ? [ + TableColumn::make(__('custom-fields::custom-fields.field.form.options.category')) + ->width('200px') + ->hiddenHeaderLabel(), + ] : []), ]) ->schema([ ColorPicker::make('settings.color') @@ -186,15 +679,15 @@ public static function schema(bool $withOptionsRelationship = true, ?CustomField ->required() ->columnSpan(9) ->rules([ - fn (Get $get): Closure => function (string $attribute, $value, Closure $fail) use ($get): void { + fn (Get $get): Closure => function (string $attribute, mixed $value, Closure $fail) use ($get): void { if (blank($value)) { return; } - $hasDuplicate = collect($get('../../options') ?? []) + $hasDuplicate = collect(Arr::wrap($get('../../options'))) ->pluck('name') ->filter() - ->map(fn ($name): string => mb_strtolower($name)) + ->map(fn (string $name): string => mb_strtolower($name)) ->duplicates() ->contains(mb_strtolower($value)); @@ -203,6 +696,10 @@ public static function schema(bool $withOptionsRelationship = true, ?CustomField } }, ]), + Select::make('settings.category') + ->options(OptionCategory::class) + ->placeholder(__('custom-fields::custom-fields.field.form.options.category_placeholder')) + ->visible(fn (Get $get): bool => self::showsOptionCategories($get('../../type'))), ]) ->columns(12) ->columnSpanFull() @@ -215,7 +712,10 @@ public static function schema(bool $withOptionsRelationship = true, ?CustomField return CustomFieldsType::toCollection()->acceptsArbitraryValues()->pluck('key')->toArray(); }) ->hiddenLabel() - ->defaultItems(1) + // A blank row the user never typed fails the name rule on the one type whose + // options are optional, so the first row comes from the add action instead. + ->defaultItems(0) + ->hintAction(self::pasteOptionsAction()) ->addActionLabel( __('custom-fields::custom-fields.field.form.options.add') ) @@ -225,7 +725,7 @@ public static function schema(bool $withOptionsRelationship = true, ?CustomField fn (Get $get): bool => $get('type') !== null && CustomFieldsType::getFieldType($get('type'))->dataType->isChoiceField() && ! CustomFieldsType::getFieldType($get('type'))->withoutUserOptions - && ! CustomFieldsType::getFieldType($get('type'))->requiresLookupType + && ! CustomFieldsType::getFieldType($get('type'))->requiresRelationship ) ->mutateRelationshipDataBeforeCreateUsing(function ( array $data @@ -234,6 +734,19 @@ public static function schema(bool $withOptionsRelationship = true, ?CustomField $data[config('custom-fields.database.column_names.tenant_foreign_key')] = TenantContextService::getCurrentTenantId(); } + return $data; + }) + ->mutateRelationshipDataBeforeSaveUsing(function (array $data, Model $record): array { + // A hidden column is never dehydrated, so a submitted item carries only the + // settings the editor showed and would rewrite the row without the rest. + $stored = $record->getAttribute('settings'); + $submitted = $data['settings'] ?? null; + + $data['settings'] = [ + ...($stored instanceof CustomFieldOptionSettingsData ? $stored->toArray() : []), + ...(is_array($submitted) ? $submitted : []), + ]; + return $data; }); @@ -247,13 +760,13 @@ public static function schema(bool $withOptionsRelationship = true, ?CustomField $generalSchema = [ Hidden::make('entity_type') ->default( - fn () => request( + fn (): mixed => $entityType ?? request( 'entityType', (Entities::withCustomFields()->first()?->getAlias()) ?? '' ) ), Grid::make() - ->columns(fn (): int => FeatureManager::isEnabled(CustomFieldsFeature::FIELD_CODE_AUTO_GENERATE) ? 2 : 3) + ->columns(2) ->columnSpanFull() ->schema([ TypeField::make('type') @@ -317,39 +830,6 @@ public static function schema(bool $withOptionsRelationship = true, ?CustomField return; } - $set('code', Str::of($state)->slug('_')->toString()); - }), - TextInput::make('code') - ->label(__('custom-fields::custom-fields.field.form.code')) - ->live(onBlur: true) - ->required(fn (): bool => ! FeatureManager::isEnabled(CustomFieldsFeature::FIELD_CODE_AUTO_GENERATE)) - ->alphaDash() - ->maxLength(50) - ->disabled(self::disabledForSystemFields()) - ->visible(fn (): bool => ! FeatureManager::isEnabled(CustomFieldsFeature::FIELD_CODE_AUTO_GENERATE)) - ->unique( - table: CustomFields::customFieldModel(), - column: 'code', - ignoreRecord: true, - modifyRuleUsing: function (Unique $rule, Get $get) use ($uniqueCodeRuleModifier): Unique { - $rule = $rule - ->where('entity_type', $get('entity_type')) - ->when( - FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY), - fn (Unique $rule) => $rule->where( - config('custom-fields.database.column_names.tenant_foreign_key'), - TenantContextService::getCurrentTenantId() - ) - ); - - if ($uniqueCodeRuleModifier instanceof Closure) { - return $uniqueCodeRuleModifier($rule, $get); - } - - return $rule; - } - ) - ->afterStateUpdated(function (Set $set, ?string $state): void { $set('code', Str::of($state)->slug('_')->toString()); }), ]), @@ -495,7 +975,8 @@ public static function schema(bool $withOptionsRelationship = true, ?CustomField ): bool => FeatureManager::isEnabled(CustomFieldsFeature::FIELD_OPTION_COLORS) && in_array((string) $get('type'), [ 'select', - 'multi_select', + StatusFieldType::KEY, + 'multi-select', 'tags-input', ], true) ), @@ -531,7 +1012,7 @@ public static function schema(bool $withOptionsRelationship = true, ?CustomField return FeatureManager::isEnabled(CustomFieldsFeature::FIELD_MULTI_VALUE) && $fieldType?->supportsMultiValue === true && - $fieldType->requiresLookupType !== true && + $fieldType->requiresRelationship !== true && $get('settings.allow_multiple') === true; }), // Uniqueness constraint @@ -556,19 +1037,12 @@ public static function schema(bool $withOptionsRelationship = true, ?CustomField ]; - $generalSchema[] = Select::make('lookup_type') - ->label(__('custom-fields::custom-fields.field.form.lookup_type.label')) - ->visible( - fn (Get $get): bool => $get('type') !== null - && CustomFieldsType::getFieldType($get('type'))?->requiresLookupType === true - ) - ->disabled(fn (?CustomField $record): bool => (bool) $record?->exists) - ->options(Entities::getLookupOptions()) - ->default((Entities::asLookupSources()->first()?->getAlias()) ?? '') - ->required(); + $generalSchema[] = self::recordConfiguration(); $generalSchema[] = $optionsRepeater; + $generalSchema[] = self::advancedDisclosure($uniqueCodeRuleModifier); + // Build additional tabs based on feature flags $additionalTabs = []; diff --git a/src/Filament/Management/Schemas/SectionForm.php b/src/Filament/Management/Schemas/SectionForm.php index a4150d4a..99357a24 100644 --- a/src/Filament/Management/Schemas/SectionForm.php +++ b/src/Filament/Management/Schemas/SectionForm.php @@ -23,7 +23,7 @@ use Relaticle\CustomFields\Models\CustomFieldSection; use Relaticle\CustomFields\Services\TenantContextService; -class SectionForm implements FormInterface, SectionFormInterface +final class SectionForm implements FormInterface, SectionFormInterface { private static string $entityType; diff --git a/src/Http/Middleware/SetTenantContextMiddleware.php b/src/Http/Middleware/SetTenantContextMiddleware.php index 96ca55aa..138be14e 100644 --- a/src/Http/Middleware/SetTenantContextMiddleware.php +++ b/src/Http/Middleware/SetTenantContextMiddleware.php @@ -11,7 +11,7 @@ use Relaticle\CustomFields\Services\TenantContextService; use Symfony\Component\HttpFoundation\Response; -class SetTenantContextMiddleware +final class SetTenantContextMiddleware { /** * Handle an incoming request. diff --git a/src/Livewire/Concerns/CreatesCustomFields.php b/src/Livewire/Concerns/CreatesCustomFields.php deleted file mode 100644 index e67e03ea..00000000 --- a/src/Livewire/Concerns/CreatesCustomFields.php +++ /dev/null @@ -1,61 +0,0 @@ - $entityType, - ]; - - if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS) && $sectionId !== null) { - $result['custom_field_section_id'] = $sectionId; - } - - return $result; - } - - protected function storeField(array $data): void - { - $data = DateConstraintField::sanitizeValidationRules($data); - - $options = collect($data['options'] ?? []) - ->filter() - ->values() - ->map(function (array $option, int $index): array { - $option['sort_order'] = $index; - - if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { - $option[config('custom-fields.database.column_names.tenant_foreign_key')] = TenantContextService::getCurrentTenantId(); - } - - return $option; - }); - - unset($data['options']); - - $customField = CustomFields::newCustomFieldModel()->create($data); - - $customField->options()->createMany($options); - } -} diff --git a/src/Livewire/Concerns/ManagesCustomFields.php b/src/Livewire/Concerns/ManagesCustomFields.php new file mode 100644 index 00000000..c503e112 --- /dev/null +++ b/src/Livewire/Concerns/ManagesCustomFields.php @@ -0,0 +1,254 @@ + + */ + protected function submitsOnMetaEnter(): array + { + return [ + 'x-on:keydown.meta.enter.prevent' => '$el.requestSubmit()', + 'x-on:keydown.ctrl.enter.prevent' => '$el.requestSubmit()', + ]; + } + + /** + * @param array $data + * @return array + */ + protected function mutateFieldData(array $data, string $entityType, int|string|null $sectionId = null): array + { + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $data[config('custom-fields.database.column_names.tenant_foreign_key')] = TenantContextService::getCurrentTenantId(); + } + + if (FeatureManager::isEnabled(CustomFieldsFeature::FIELD_CODE_AUTO_GENERATE) && blank($data['code'] ?? null)) { + $data['code'] = CodeGenerator::generateUniqueFieldCode($data['name'], $entityType, sectionId: $sectionId); + } + + $result = [ + ...$data, + 'entity_type' => $entityType, + ]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS) && $sectionId !== null) { + $result['custom_field_section_id'] = $sectionId; + } + + return $result; + } + + /** + * @param array $data + */ + protected function storeField(array $data): CustomField + { + $data = DateConstraintField::sanitizeValidationRules($data); + $relationship = $this->pullRelationshipData($data); + + $options = collect(Arr::wrap($data['options'] ?? [])) + ->filter() + ->values() + ->map(function (array $option, int $index): array { + $option['sort_order'] = $index; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $option[config('custom-fields.database.column_names.tenant_foreign_key')] = TenantContextService::getCurrentTenantId(); + } + + return $option; + }); + + unset($data['options']); + + // A record field with no definition points nowhere, so the field and the definition + // are one write: a rejected definition takes the field with it. + return DB::transaction(function () use ($data, $options, $relationship): CustomField { + $customField = CustomFields::newCustomFieldModel()->create($data); + + $customField->options()->createMany($options); + + if ($relationship !== null) { + $this->defineRelationship($customField, $relationship); + } + + return $customField; + }); + } + + /** + * @param array $data + * + * @throws ValidationException + */ + protected function updateField(CustomField $field, array $data): void + { + $data = DateConstraintField::sanitizeValidationRules($data); + $relationship = $this->pullRelationshipData($data); + + if (isset($data['settings'])) { + $data['settings'] = array_merge($field->settings->toArray(), $data['settings']); + } + + DB::transaction(function () use ($field, $data, $relationship): void { + $field->update($data); + + $definition = $field->relationshipDefinition(); + + if ($relationship === null || ! $definition instanceof CustomFieldRelationship) { + return; + } + + app(UpdateRelationshipDefinition::class)->execute( + $definition, + $definition->orientCardinality($field, $this->resolvedCardinality( + $relationship, + $definition->orientCardinality($field, $definition->cardinality), + )), + keepFirst: ($relationship['keep_first'] ?? false) === true, + ); + }); + } + + /** + * @return array + */ + protected function fieldFormState(CustomField $field): array + { + return [ + ...$field->toArray(), + 'options' => $field->options->toArray(), + 'relationship' => FieldForm::relationshipState($field), + ]; + } + + /** + * A copy of a record field needs a definition of its own: the field row carries no target, + * and a record field without one points nowhere. The copy is always unpaired. + */ + protected function copyRelationship(CustomField $field, CustomField $copy): void + { + $definition = $field->relationshipDefinition(); + $targetEntityType = $field->targetEntityType(); + + if (! $definition instanceof CustomFieldRelationship || $targetEntityType === null) { + return; + } + + // The copy renders the from end of its own definition, so a source that reads the to + // end hands over the cardinality the way it sees it, not the way it is stored. + $this->defineRelationship($copy, [ + 'target_entity_type' => $targetEntityType, + 'cardinality' => $definition->orientCardinality($field, $definition->cardinality)->value, + 'is_symmetric' => $definition->is_symmetric, + ]); + } + + /** + * The record configuration is form state, never a column, so it leaves the payload before + * the field row is written. + * + * @param array $data + * @return array|null + */ + private function pullRelationshipData(array &$data): ?array + { + $relationship = $data['relationship'] ?? null; + + unset($data['relationship']); + + if (! is_array($relationship)) { + return null; + } + + return filled($relationship['cardinality'] ?? null) || array_key_exists('allow_multiple', $relationship) + ? $relationship + : null; + } + + /** + * The cardinality the submitted configuration asks for, as the field's own end reads it. + * + * The paired face names it outright. The one-way face asks only how many records this + * field holds, which is one end of the answer: the other end keeps the constraint it + * already had, or a save that merely renamed the field would free the end the move + * confirmation is read from. + * + * @param array $relationship + * @param ?RelationshipCardinality $current what the field holds today, as its own end + * reads it; a field being created holds nothing + */ + private function resolvedCardinality(array $relationship, ?RelationshipCardinality $current): RelationshipCardinality + { + if (filled($relationship['cardinality'] ?? null)) { + return RelationshipCardinality::from((string) $relationship['cardinality']); + } + + return ($current ?? RelationshipCardinality::ManyToOne) + ->fromSideHolds(($relationship['allow_multiple'] ?? false) === true); + } + + /** + * @param array $relationship + */ + private function defineRelationship(CustomField $field, array $relationship): void + { + $fromEntityType = $this->resolveEntityType((string) $field->entity_type); + $toEntityType = $this->resolveEntityType((string) $relationship['target_entity_type']); + $isSymmetric = ($relationship['is_symmetric'] ?? false) === true && $fromEntityType === $toEntityType; + $pairedName = trim((string) ($relationship['paired_field_name'] ?? '')); + + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: CodeGenerator::generateUniqueRelationshipCode($field->code), + fromEntityType: $fromEntityType, + toEntityType: $toEntityType, + cardinality: $this->resolvedCardinality($relationship, null), + isSymmetric: $isSymmetric, + fromField: new FieldSlotData(name: $field->name, fieldId: $field->getKey()), + toField: $isSymmetric || $pairedName === '' + ? null + : new FieldSlotData( + name: $pairedName, + sectionId: $relationship['paired_section_id'] ?? null, + type: (string) $field->type, + ), + )); + } + + /** + * Links carry morph classes, and an entity is registered under that same alias, so both + * ends go through the registry rather than through whatever string the form held. + */ + private function resolveEntityType(string $entityType): string + { + return Entities::getEntity($entityType)?->getAlias() ?? $entityType; + } +} diff --git a/src/Livewire/Concerns/ManagesFields.php b/src/Livewire/Concerns/ManagesFields.php index c12d3e21..73c2eecd 100644 --- a/src/Livewire/Concerns/ManagesFields.php +++ b/src/Livewire/Concerns/ManagesFields.php @@ -8,12 +8,16 @@ use Livewire\Attributes\Computed; use Livewire\Attributes\On; use Relaticle\CustomFields\CustomFields; +use Relaticle\CustomFields\Models\CustomField; /** * Shared logic for managing custom fields in Livewire components. */ trait ManagesFields { + /** + * @return Collection + */ #[Computed] public function fields(): Collection { diff --git a/src/Livewire/ManageCustomField.php b/src/Livewire/ManageCustomField.php index 093b33b5..27d51554 100644 --- a/src/Livewire/ManageCustomField.php +++ b/src/Livewire/ManageCustomField.php @@ -12,12 +12,14 @@ use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Contracts\HasForms; use Filament\Support\Enums\Width; +use Illuminate\Contracts\View\View; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\View as ViewFactory; use Illuminate\Support\Str; -use Illuminate\View\View; use Livewire\Component; use Relaticle\CustomFields\CustomFields; -use Relaticle\CustomFields\Filament\Management\Forms\Components\DateConstraintField; use Relaticle\CustomFields\Filament\Management\Schemas\FieldForm; +use Relaticle\CustomFields\Livewire\Concerns\ManagesCustomFields; use Relaticle\CustomFields\Models\CustomField; final class ManageCustomField extends Component implements HasActions, HasForms @@ -25,6 +27,7 @@ final class ManageCustomField extends Component implements HasActions, HasForms use InteractsWithActions; use InteractsWithForms; use InteractsWithRecord; + use ManagesCustomFields; public CustomField $field; @@ -48,22 +51,10 @@ public function editAction(): Action ->model(CustomFields::customFieldModel()) ->record($this->field) ->schema(FieldForm::schema(section: $this->field->section)) - ->fillForm(function (): array { - $data = $this->field->toArray(); - $data['options'] = $this->field->options->toArray(); - - return $data; - }) - ->action(function (array $data): void { - $data = DateConstraintField::sanitizeValidationRules($data); - - if (isset($data['settings'])) { - $data['settings'] = array_merge($this->field->settings->toArray(), $data['settings']); - } - - $this->field->update($data); - }) + ->fillForm(fn (): array => $this->fieldFormState($this->field)) + ->action(fn (array $data) => $this->updateField($this->field, $data)) ->modalWidth(Width::ScreenLarge) + ->extraModalWindowAttributes($this->submitsOnMetaEnter()) ->slideOver(); } @@ -82,22 +73,26 @@ public function duplicateAction(): Action $this->field->entity_type ); - $clone = $this->field->replicate([ - 'id', 'created_at', 'updated_at', - ]); - $clone->name = $this->field->name.' (Copy)'; - $clone->code = $code; - $clone->system_defined = false; - $clone->active = true; - $clone->save(); - - foreach ($this->field->options as $option) { - $clone->options()->create([ - 'name' => $option->getRawOriginal('name'), - 'sort_order' => $option->sort_order, - 'settings' => $option->settings, + DB::transaction(function () use ($code): void { + $clone = $this->field->replicate([ + 'id', 'created_at', 'updated_at', ]); - } + $clone->name = $this->field->name.' (Copy)'; + $clone->code = $code; + $clone->system_defined = false; + $clone->active = true; + $clone->save(); + + foreach ($this->field->options as $option) { + $clone->options()->create([ + 'name' => $option->getRawOriginal('name'), + 'sort_order' => $option->sort_order, + 'settings' => $option->settings, + ]); + } + + $this->copyRelationship($this->field, $clone); + }); $this->dispatch('field-created'); }); @@ -167,6 +162,6 @@ public function deleteAction(): Action public function render(): View { - return view('custom-fields::livewire.manage-custom-field'); + return ViewFactory::make('custom-fields::livewire.manage-custom-field'); } } diff --git a/src/Livewire/ManageCustomFieldSection.php b/src/Livewire/ManageCustomFieldSection.php index 98cb829f..25f69c5e 100644 --- a/src/Livewire/ManageCustomFieldSection.php +++ b/src/Livewire/ManageCustomFieldSection.php @@ -15,21 +15,23 @@ use Filament\Support\Enums\Size; use Filament\Support\Enums\Width; use Illuminate\Contracts\View\View; -use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\View as ViewFactory; use Livewire\Component; use Relaticle\CustomFields\CustomFields; use Relaticle\CustomFields\CustomFieldsPlugin; use Relaticle\CustomFields\Filament\Management\Schemas\FieldForm; use Relaticle\CustomFields\Filament\Management\Schemas\SectionForm; -use Relaticle\CustomFields\Livewire\Concerns\CreatesCustomFields; +use Relaticle\CustomFields\Livewire\Concerns\ManagesCustomFields; use Relaticle\CustomFields\Livewire\Concerns\ManagesFields; +use Relaticle\CustomFields\Models\CustomField; use Relaticle\CustomFields\Models\CustomFieldSection; +use Relaticle\CustomFields\Models\Scopes\SortOrderScope; final class ManageCustomFieldSection extends Component implements HasActions, HasForms { - use CreatesCustomFields; use InteractsWithActions; use InteractsWithForms; + use ManagesCustomFields; use ManagesFields; /** @var ?Closure(CustomFieldSection): ?Closure */ @@ -44,6 +46,9 @@ public static function resolveUniqueRuleModifierUsing(?Closure $callback): void self::$uniqueRuleModifierResolver = $callback; } + /** + * @param array $fields + */ public function updateFieldsOrder(int|string $sectionId, array $fields): void { $model = CustomFields::newCustomFieldModel(); @@ -80,9 +85,10 @@ public function updateFieldsOrder(int|string $sectionId, array $fields): void /** * @param array $fieldIds */ - private function fieldsHaveDuplicateCode(Model $model, array $fieldIds): bool + private function fieldsHaveDuplicateCode(CustomField $model, array $fieldIds): bool { return $model->query() + ->withoutGlobalScope(SortOrderScope::class) ->withDeactivated() ->whereIn($model->getKeyName(), $fieldIds) ->select('code') @@ -195,16 +201,16 @@ public function createFieldAction(): Action ->size(Size::ExtraSmall) ->label(__('custom-fields::custom-fields.field.form.add_field')) ->model(CustomFields::customFieldModel()) - ->schema(FieldForm::schema(withOptionsRelationship: false, section: $this->section)) - ->fillForm(['entity_type' => $this->entityType]) + ->schema(FieldForm::schema(withOptionsRelationship: false, section: $this->section, entityType: $this->entityType)) ->mutateDataUsing(fn (array $data): array => $this->mutateFieldData($data, $this->entityType, $this->section->getKey())) - ->action(fn (array $data) => $this->storeField($data)) + ->action(fn (array $data): CustomField => $this->storeField($data)) ->modalWidth(Width::ScreenLarge) + ->extraModalWindowAttributes($this->submitsOnMetaEnter()) ->slideOver(); } public function render(): View { - return view('custom-fields::livewire.manage-custom-field-section'); + return ViewFactory::make('custom-fields::livewire.manage-custom-field-section'); } } diff --git a/src/Livewire/ManageCustomFieldWidth.php b/src/Livewire/ManageCustomFieldWidth.php index 48ba5aa6..e9e65524 100644 --- a/src/Livewire/ManageCustomFieldWidth.php +++ b/src/Livewire/ManageCustomFieldWidth.php @@ -5,39 +5,22 @@ namespace Relaticle\CustomFields\Livewire; use Illuminate\Contracts\View\View; +use Illuminate\Support\Facades\View as ViewFactory; use Livewire\Component; use Relaticle\CustomFields\Enums\CustomFieldWidth; -class ManageCustomFieldWidth extends Component +final class ManageCustomFieldWidth extends Component { - /** - * @var int - */ - public $selectedWidth = 100; // @pest-ignore-type + public CustomFieldWidth $selectedWidth = CustomFieldWidth::_100; /** * @var array */ - public $widthOptions = [ // @pest-ignore-type + public array $widthOptions = [ 25, 33, 50, 66, 75, 100, ]; - /** - * @var array - */ - public $widthMap = [ // @pest-ignore-type - '25' => 'col-span-3', - '33' => 'col-span-4', - '50' => 'col-span-6', - '66' => 'col-span-8', - '75' => 'col-span-9', - '100' => 'col-span-12', - ]; - - /** - * @var int|string - */ - public $fieldId; // @pest-ignore-type + public int|string $fieldId; public function mount(CustomFieldWidth $selectedWidth, int|string $fieldId): void { @@ -47,6 +30,6 @@ public function mount(CustomFieldWidth $selectedWidth, int|string $fieldId): voi public function render(): View { - return view('custom-fields::livewire.manage-custom-field-width'); + return ViewFactory::make('custom-fields::livewire.manage-custom-field-width'); } } diff --git a/src/Livewire/ManageFieldsTable.php b/src/Livewire/ManageFieldsTable.php index 939014a0..5806efb9 100644 --- a/src/Livewire/ManageFieldsTable.php +++ b/src/Livewire/ManageFieldsTable.php @@ -14,13 +14,18 @@ use Illuminate\Contracts\View\View; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Facades\View as ViewFactory; use Livewire\Attributes\Computed; use Livewire\Attributes\On; use Livewire\Component; use Relaticle\CustomFields\CustomFields; +use Relaticle\CustomFields\Enums\UiSurface; +use Relaticle\CustomFields\Facades\Entities; use Relaticle\CustomFields\Filament\Management\Schemas\FieldForm; -use Relaticle\CustomFields\Livewire\Concerns\CreatesCustomFields; +use Relaticle\CustomFields\Livewire\Concerns\ManagesCustomFields; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Support\RelationshipTables; +use Relaticle\CustomFields\Support\ViewFlavor; /** * Livewire component for managing custom fields in a flat table layout. @@ -30,9 +35,9 @@ */ final class ManageFieldsTable extends Component implements HasActions, HasForms { - use CreatesCustomFields; use InteractsWithActions; use InteractsWithForms; + use ManagesCustomFields; public string $entityType; @@ -52,6 +57,74 @@ public function inactiveFields(): Collection return $this->getFieldsQuery()->where('active', false)->get(); } + /** + * The relationship each paired field in this table belongs to, resolved in a fixed number + * of queries (the definitions plus one eager load per slot) rather than once per row. The + * partner is read through the relation so it keeps the tenant and activable scopes a + * hand-rolled subselect would drop. A pair whose other end is on this same entity carries + * the partner id, which is what connects the two rows. + * + * @return array + */ + #[Computed] + public function relationshipPairs(): array + { + if (! RelationshipTables::exist()) { + return []; + } + + $fields = $this->activeFields() + ->concat($this->inactiveFields()) + ->filter(fn (CustomField $field): bool => $field->supportsPairing()) + ->keyBy(fn (CustomField $field): string => (string) $field->getKey()); + + if ($fields->isEmpty()) { + return []; + } + + $keys = $fields->map(fn (CustomField $field): int|string => $field->getKey())->values()->all(); + + $definitions = CustomFields::newRelationshipModel() + ->newQuery() + ->with(['fromField', 'toField']) + ->where(function (Builder $query) use ($keys): void { + $query->whereIn('from_field_id', $keys)->orWhereIn('to_field_id', $keys); + }) + ->get(); + + $pairs = []; + + foreach ($definitions as $definition) { + $ends = [ + [$definition->from_field_id, $definition->toField, $definition->to_entity_type], + [$definition->to_field_id, $definition->fromField, $definition->from_entity_type], + ]; + + foreach ($ends as [$fieldId, $partner, $entityType]) { + if ($fieldId === null || ! $fields->has((string) $fieldId)) { + continue; + } + + $partnerIsVisible = ! $definition->is_symmetric + && $partner instanceof CustomField + && $fields->has((string) $partner->getKey()); + + $pairs[(string) $fieldId] = [ + 'definition' => (string) $definition->getKey(), + 'partner_id' => $partnerIsVisible ? (string) $partner->getKey() : null, + 'partner_name' => $definition->is_symmetric ? null : $partner?->name, + 'entity' => Entities::getEntity($entityType)?->getLabelSingular(), + 'symmetric' => $definition->is_symmetric, + ]; + } + } + + return $pairs; + } + + /** + * @return Builder + */ private function getFieldsQuery(): Builder { return CustomFields::newCustomFieldModel() @@ -72,9 +145,12 @@ private function findField(string|int $fieldId): ?CustomField private function resetFieldsCache(): void { - unset($this->activeFields, $this->inactiveFields); + unset($this->activeFields, $this->inactiveFields, $this->relationshipPairs); } + /** + * @param array $order + */ public function updateFieldsOrder(array $order): void { foreach ($order as $index => $id) { @@ -100,16 +176,13 @@ public function editFieldAction(): Action ->model(CustomFields::customFieldModel()) ->record(fn (array $arguments): ?CustomField => $this->findField($arguments['fieldId'])) ->schema(FieldForm::schema(withOptionsRelationship: true)) - ->fillForm(fn (CustomField $record): array => $record->toArray()) + ->fillForm(fn (CustomField $record): array => $this->fieldFormState($record)) ->action(function (array $data, CustomField $record): void { - if (isset($data['settings'])) { - $data['settings'] = array_merge($record->settings->toArray(), $data['settings']); - } - - $record->update($data); + $this->updateField($record, $data); $this->resetFieldsCache(); }) ->modalWidth(Width::ScreenLarge) + ->extraModalWindowAttributes($this->submitsOnMetaEnter()) ->slideOver(); } @@ -185,19 +258,21 @@ public function createFieldAction(): Action 'class' => 'flex justify-center items-center rounded-lg border-gray-300 hover:border-gray-400 border-dashed', ]) ->model(CustomFields::customFieldModel()) - ->schema(FieldForm::schema(withOptionsRelationship: false)) - ->fillForm(['entity_type' => $this->entityType]) + ->schema(FieldForm::schema(withOptionsRelationship: false, entityType: $this->entityType)) ->mutateDataUsing(fn (array $data): array => $this->mutateFieldData($data, $this->entityType)) ->action(function (array $data): void { $this->storeField($data); $this->resetFieldsCache(); }) ->modalWidth(Width::ScreenLarge) + ->extraModalWindowAttributes($this->submitsOnMetaEnter()) ->slideOver(); } public function render(): View { - return view('custom-fields::livewire.manage-fields-table'); + return ViewFactory::make( + ViewFlavor::view(UiSurface::AttributeTable) ?? 'custom-fields::livewire.manage-fields-table' + ); } } diff --git a/src/Models/Concerns/HasFieldType.php b/src/Models/Concerns/HasFieldType.php index 1e4355f0..93aa6585 100644 --- a/src/Models/Concerns/HasFieldType.php +++ b/src/Models/Concerns/HasFieldType.php @@ -1,5 +1,7 @@ customFieldValues()->delete(); + $model->deleteCustomFieldLinks(); }); } + /** + * The saved hook writes custom fields after the record row is already written, and a + * rejected link throws there, so a pending payload puts the whole save in one + * transaction (a savepoint when the host already opened one). + * + * @param array $options + */ + public function save(array $options = []): bool + { + if (! $this->hasPendingCustomFields()) { + return parent::save($options); + } + + return (bool) $this->getConnection()->transaction(fn (): bool => parent::save($options)); + } + + /** + * A payload reaches save() either as an attribute or, when it came through the + * constructor, already parked in the temporary store. + */ + protected function hasPendingCustomFields(): bool + { + if (isset($this->custom_fields) && is_array($this->custom_fields)) { + return true; + } + + return isset(self::$tempCustomFields[spl_object_id($this)]); + } + /** * Handle the custom fields before saving the model. */ @@ -102,8 +139,15 @@ protected function saveCustomFieldsFromTemp(): void { $objectId = spl_object_id($this); - if (isset(self::$tempCustomFields[$objectId]) && method_exists($this, 'saveCustomFields')) { + if (! isset(self::$tempCustomFields[$objectId]) || ! method_exists($this, 'saveCustomFields')) { + return; + } + + // A rejected payload rolls its record back, and the store is keyed on an object id + // PHP reuses after collection, so the entry goes whichever way the write ends. + try { $this->saveCustomFields(self::$tempCustomFields[$objectId]); + } finally { unset(self::$tempCustomFields[$objectId]); } } @@ -124,9 +168,68 @@ public function customFieldValues(): MorphMany return $this->morphMany(CustomFields::valueModel(), 'entity'); } + /** + * @return MorphMany + */ + public function outgoingLinks(): MorphMany + { + return $this->morphMany(CustomFields::linkModel(), 'from_entity'); + } + + /** + * @return MorphMany + */ + public function incomingLinks(): MorphMany + { + return $this->morphMany(CustomFields::linkModel(), 'to_entity'); + } + + /** + * A record that is really gone leaves no edge behind, in either direction and not in + * history either: one delete per end, each on its own reverse index. A soft delete + * never reaches this, so a restored record finds its links where it left them. + * + * The tenant scope is dropped because the record is already identified, and no context + * a delete happens in may strand an edge. + */ + protected function deleteCustomFieldLinks(): void + { + if (! RelationshipTables::exist()) { + return; + } + + $ends = [CustomFieldRelationship::DIRECTION_FROM, CustomFieldRelationship::DIRECTION_TO]; + + foreach ($ends as $end) { + CustomFields::newLinkModel() + ->newQuery() + ->withoutGlobalScope(TenantScope::class) + ->where($end.'_entity_type', $this->getMorphClass()) + ->where($end.'_entity_id', $this->getKey()) + ->delete(); + } + } + + /** + * The ledger keeps closed edges forever, so only the active ones are worth carrying + * into a page render. + */ + public function scopeWithActiveCustomFieldLinks(Builder $query): Builder + { + if (! RelationshipTables::exist()) { + return $query; + } + + return $query->with([ + 'outgoingLinks' => fn (MorphMany $links): MorphMany => $links->whereNull('active_until'), + 'incomingLinks' => fn (MorphMany $links): MorphMany => $links->whereNull('active_until'), + ]); + } + public function scopeWithCustomFieldValues(Builder $query): Builder { return $query + ->withActiveCustomFieldLinks() ->with('customFieldValues.customField.options') ->afterQuery(function ($records): void { if ($records instanceof EloquentCollection) { @@ -137,6 +240,12 @@ public function scopeWithCustomFieldValues(Builder $query): Builder public function getCustomFieldValue(CustomField $customField): mixed { + $definition = $customField->relationshipDefinition(); + + if ($definition instanceof CustomFieldRelationship) { + return app(LinkReader::class)->orderedIdsFor($this, $definition, $definition->readDirectionFor($customField)); + } + $fieldValue = $this->customFieldValues ->firstWhere('custom_field_id', $customField->getKey()) ?->getValue(); @@ -156,6 +265,14 @@ public function getCustomFieldValue(CustomField $customField): mixed public function saveCustomFieldValue(CustomField $customField, mixed $value, ?Model $tenant = null): void { + if ($this->writesLinksFor($customField)) { + $payload = RecordLinkPayload::fromValue($value); + + app(LinkWriter::class)->apply($this, $customField, $payload->ids, confirmed: $payload->confirmed); + + return; + } + $data = ['custom_field_id' => $customField->getKey()]; if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { @@ -173,6 +290,16 @@ public function saveCustomFieldValue(CustomField $customField, mixed $value, ?Mo $customFieldValue->save(); } + /** + * A definition is the write fork, exactly as it already is for reads: no definition can + * exist without the tables it lives in, so a host that never enabled the feature keeps + * writing value rows either way. + */ + protected function writesLinksFor(CustomField $customField): bool + { + return $customField->relationshipDefinition() instanceof CustomFieldRelationship; + } + /** * Resolve the tenant ID from available sources */ @@ -201,6 +328,12 @@ protected function resolveTenantId(?Model $tenant, CustomField $customField): mi public function saveCustomFields(array $customFields, ?Model $tenant = null): void { $this->customFields()->each(function (CustomField $customField) use ($customFields, $tenant): void { + // A relationship has no row to overwrite with null: an absent key means the + // payload said nothing about those edges, so they stay as they are. + if (! array_key_exists($customField->code, $customFields) && $this->writesLinksFor($customField)) { + return; + } + $value = $customFields[$customField->code] ?? null; $this->saveCustomFieldValue($customField, $value, $tenant); }); diff --git a/src/Models/CustomField.php b/src/Models/CustomField.php index 6b1b2392..4e7e919d 100644 --- a/src/Models/CustomField.php +++ b/src/Models/CustomField.php @@ -6,13 +6,16 @@ use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Attributes\ScopedBy; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\AsCollection; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Schema; use Override; use Relaticle\CustomFields\CustomFields; use Relaticle\CustomFields\Data\CustomFieldSettingsData; @@ -21,6 +24,8 @@ use Relaticle\CustomFields\Database\Factories\CustomFieldFactory; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\Enums\CustomFieldWidth; +use Relaticle\CustomFields\Enums\OptionCategory; +use Relaticle\CustomFields\Exceptions\RelationshipDefinitionDoesNotExistException; use Relaticle\CustomFields\Facades\CustomFieldsType; use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Models\Concerns\Activable; @@ -36,25 +41,25 @@ * @property string $code * @property string $type * @property string $entity_type - * @property ?string $lookup_type - * @property Collection $validation_rules + * @property Collection $validation_rules * @property CustomFieldSettingsData $settings * @property int $sort_order * @property bool $active * @property bool $system_defined * @property FieldTypeData $typeData * @property CustomFieldWidth $width + * @property-read ?CustomFieldSection $section * - * @method static CustomFieldQueryBuilder query() - * @method static CustomFieldQueryBuilder where($column, $operator = null, $value = null, $boolean = 'and') - * @method static CustomFieldQueryBuilder whereIn($column, $values, $boolean = 'and', $not = false) - * @method static CustomFieldQueryBuilder active() - * @method static CustomFieldQueryBuilder visibleInList() - * @method static CustomFieldQueryBuilder nonEncrypted() - * @method static CustomFieldQueryBuilder forEntity(string $model) - * @method static CustomFieldQueryBuilder forMorphEntity(string $entity) - * @method static CustomFieldQueryBuilder forType(string $type) - * @method static CustomFieldQueryBuilder withDeactivated(bool $withDeactivated = true) + * @method static CustomFieldQueryBuilder query() + * @method static CustomFieldQueryBuilder where($column, $operator = null, $value = null, $boolean = 'and') + * @method static CustomFieldQueryBuilder whereIn($column, $values, $boolean = 'and', $not = false) + * @method static CustomFieldQueryBuilder active() + * @method static CustomFieldQueryBuilder visibleInList() + * @method static CustomFieldQueryBuilder nonEncrypted() + * @method static CustomFieldQueryBuilder forEntity(string $model) + * @method static CustomFieldQueryBuilder forMorphEntity(string $entity) + * @method static CustomFieldQueryBuilder forType(string $type) + * @method static CustomFieldQueryBuilder withDeactivated(bool $withDeactivated = true) */ #[ScopedBy([TenantScope::class, SortOrderScope::class])] #[ObservedBy(CustomFieldObserver::class)] @@ -155,6 +160,105 @@ public function options(): HasMany ->orderBy('sort_order'); } + /** + * @return EloquentCollection + */ + public function optionsInCategory(OptionCategory $category): EloquentCollection + { + if ($this->relationLoaded('options')) { + return $this->options + ->filter(fn (CustomFieldOption $option): bool => $option->settings->category === $category) + ->values(); + } + + return $this->options()->whereCategory($category)->get(); + } + + /** + * The definition this field is a presentation slot of, from either end. + */ + public function relationshipDefinition(): ?CustomFieldRelationship + { + $key = $this->getKey(); + + // An unsaved field owns no slot, and its null key would read as whereNull and match + // every one-way definition. Returning before once() keeps nothing memoised for it. + if ($key === null) { + return null; + } + + // Only a field type that points at records is ever a slot, and every save asks each + // field in turn, so the rest never pay for a definition lookup. + if ($this->typeData?->requiresRelationship !== true) { + return null; + } + + return once(function () use ($key): ?CustomFieldRelationship { + // The feature flag gates the two migrations and the lookup_type drop, not what a + // field can read or write: a host that turns it off after migrating still has + // definitions to find, and one that never migrated has no table to look in. + if (! Schema::hasTable((string) config('custom-fields.database.table_names.custom_field_relationships'))) { + return null; + } + + return CustomFields::newRelationshipModel() + ->newQuery() + ->where(fn (Builder $query): Builder => $query + ->where('from_field_id', $key) + ->orWhere('to_field_id', $key)) + ->first(); + }); + } + + /** + * The definition a record field writes one end of. A write has no sensible answer without + * one, so it stops here; the surfaces that only render skip themselves instead. + */ + public function relationshipDefinitionOrFail(): CustomFieldRelationship + { + return $this->relationshipDefinition() + ?? throw RelationshipDefinitionDoesNotExistException::forField($this->code); + } + + /** + * Whether the field's type configures both ends of its relationship. The surfaces that + * draw chips and confirm a move belong to that type; a one-way field keeps the plain ones + * it has always had. + */ + public function supportsPairing(): bool + { + return $this->typeData?->supportsPairing === true; + } + + /** + * The entity this field points at: the far end of its relationship definition. A field + * that is not a relationship slot points nowhere. + */ + public function targetEntityType(): ?string + { + return $this->relationshipDefinition()?->targetEntityTypeFor($this); + } + + /** + * Cardinality owns multiplicity for a record field: allow_multiple describes a value row, + * and a relationship slot has none. + */ + public function allowsMultipleRecords(): bool + { + $definition = $this->relationshipDefinition(); + + if (! $definition instanceof CustomFieldRelationship) { + return $this->settings->allow_multiple; + } + + return $definition->directionFor($this) === CustomFieldRelationship::DIRECTION_TO + ? ! $definition->cardinality->toSideIsSingle() + : ! $definition->cardinality->fromSideIsSingle(); + } + + /** + * @return Attribute + */ public function typeData(): Attribute { return Attribute::make( @@ -170,9 +274,23 @@ public function isSystemDefined(): bool return $this->system_defined === true; } + /** + * A relationship slot keeps no value row, so what stands in the way of deleting it is + * an active edge on its definition. + */ public function hasValues(): bool { - return $this->values()->exists(); + $definition = $this->relationshipDefinition(); + + if (! $definition instanceof CustomFieldRelationship) { + return $this->values()->exists(); + } + + return CustomFields::newLinkModel() + ->newQuery() + ->where('relationship_id', $definition->getKey()) + ->whereNull('active_until') + ->exists(); } /** diff --git a/src/Models/CustomFieldLink.php b/src/Models/CustomFieldLink.php new file mode 100644 index 00000000..357219c8 --- /dev/null +++ b/src/Models/CustomFieldLink.php @@ -0,0 +1,138 @@ + */ + use HasFactory; + + /** + * The partial unique index the writer translates into a friendly conflict error. + */ + public const string ACTIVE_EDGE_INDEX = 'cf_links_active_edge_unique'; + + public const string SOURCE_USER = 'user'; + + public const string SOURCE_IMPORT = 'import'; + + public const string SOURCE_MIGRATION = 'migration'; + + public const string SOURCE_AI = 'ai_inferred'; + + public $timestamps = false; + + protected $guarded = []; + + /** + * @param array $attributes + */ + public function __construct(array $attributes = []) + { + if ($this->table === null) { + $this->setTable( + config('custom-fields.database.table_names.custom_field_links') + ); + } + + parent::__construct($attributes); + } + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'active_from' => 'datetime', + 'active_until' => 'datetime', + 'confidence' => 'float', + ]; + } + + /** + * @return BelongsTo + */ + public function relationship(): BelongsTo + { + return $this->belongsTo(CustomFields::relationshipModel(), 'relationship_id'); + } + + /** + * The relation name has to be the method name: an eager load initialises the relation + * under that name, and morphTo would otherwise fill a differently named one, leaving + * every eager-loaded end null while lazy access works. + * + * @return MorphTo + */ + public function fromEntity(): MorphTo + { + return $this->morphTo(__FUNCTION__, 'from_entity_type', 'from_entity_id'); + } + + /** + * @return MorphTo + */ + public function toEntity(): MorphTo + { + return $this->morphTo(__FUNCTION__, 'to_entity_type', 'to_entity_id'); + } + + /** + * @return MorphTo + */ + public function createdBy(): MorphTo + { + return $this->morphTo(__FUNCTION__, 'created_by_type', 'created_by_id'); + } + + /** + * @param Builder $query + */ + public function scopeActive(Builder $query): void + { + $query->whereNull('active_until'); + } + + /** + * An edge is closed, never deleted, so the history stays queryable. + */ + public function close(Carbon $at): void + { + $this->active_until = $at; + + $this->save(); + } +} diff --git a/src/Models/CustomFieldOption.php b/src/Models/CustomFieldOption.php index 4455cf76..87bccb7c 100644 --- a/src/Models/CustomFieldOption.php +++ b/src/Models/CustomFieldOption.php @@ -5,6 +5,7 @@ namespace Relaticle\CustomFields\Models; use Illuminate\Database\Eloquent\Attributes\ScopedBy; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -14,6 +15,7 @@ use Relaticle\CustomFields\CustomFields; use Relaticle\CustomFields\Data\CustomFieldOptionSettingsData; use Relaticle\CustomFields\Database\Factories\CustomFieldOptionFactory; +use Relaticle\CustomFields\Enums\OptionCategory; use Relaticle\CustomFields\Models\Scopes\SortOrderScope; use Relaticle\CustomFields\Models\Scopes\TenantScope; @@ -79,6 +81,8 @@ protected static function boot(): void /** * Handle decryption of option name based on parent field settings + * + * @return Attribute */ protected function name(): Attribute { @@ -126,4 +130,13 @@ public function customField(): BelongsTo /** @var BelongsTo */ return $this->belongsTo(CustomFields::customFieldModel()); } + + /** + * @param Builder $query + * @return Builder + */ + public function scopeWhereCategory(Builder $query, OptionCategory $category): Builder + { + return $query->where('settings->category', $category->value); + } } diff --git a/src/Models/CustomFieldRelationship.php b/src/Models/CustomFieldRelationship.php new file mode 100644 index 00000000..c94ec7bb --- /dev/null +++ b/src/Models/CustomFieldRelationship.php @@ -0,0 +1,161 @@ + */ + use HasFactory; + + public const string DIRECTION_FROM = 'from'; + + public const string DIRECTION_TO = 'to'; + + public const string DIRECTION_BOTH = 'both'; + + protected $guarded = []; + + /** + * @param array $attributes + */ + public function __construct(array $attributes = []) + { + if ($this->table === null) { + $this->setTable( + config('custom-fields.database.table_names.custom_field_relationships') + ); + } + + parent::__construct($attributes); + } + + /** + * @return array + */ + protected function casts(): array + { + return [ + 'cardinality' => RelationshipCardinality::class, + 'is_symmetric' => 'boolean', + ]; + } + + /** + * @return BelongsTo + */ + public function fromField(): BelongsTo + { + return $this->belongsTo(CustomFields::customFieldModel(), 'from_field_id'); + } + + /** + * @return BelongsTo + */ + public function toField(): BelongsTo + { + return $this->belongsTo(CustomFields::customFieldModel(), 'to_field_id'); + } + + /** + * @return self::DIRECTION_FROM|self::DIRECTION_TO + */ + public function directionFor(CustomField $field): string + { + $key = $field->getKey(); + + // An empty slot is null on both sides, so a keyless field would match the from slot + // of every one-way definition. + if ($key !== null) { + if ($key === $this->from_field_id) { + return self::DIRECTION_FROM; + } + + if ($key === $this->to_field_id) { + return self::DIRECTION_TO; + } + } + + throw new InvalidArgumentException(sprintf('Field [%s] does not belong to relationship [%s].', $key ?? 'unsaved', $this->code)); + } + + /** + * The entity the given slot points at: the end it does not sit on. + */ + public function targetEntityTypeFor(CustomField $field): string + { + return $this->directionFor($field) === self::DIRECTION_TO + ? $this->from_entity_type + : $this->to_entity_type; + } + + /** + * A cardinality in the given slot's own terms: a to-end field reads the definition + * backwards, so what is stored as many_to_one holds many records there. The transform is + * its own inverse, so the same call converts that field's answer back for storage. + */ + public function orientCardinality(CustomField $field, RelationshipCardinality $cardinality): RelationshipCardinality + { + return $this->directionFor($field) === self::DIRECTION_TO + ? $cardinality->inverse() + : $cardinality; + } + + /** + * A symmetric definition renders one field that reads both ends of its edges. + * + * @return self::DIRECTION_FROM|self::DIRECTION_TO|self::DIRECTION_BOTH + */ + public function readDirectionFor(CustomField $field): string + { + return $this->is_symmetric + ? self::DIRECTION_BOTH + : $this->directionFor($field); + } + + /** + * Writes always name one end: a symmetric edge is stored from the canonical side, so + * both of its slots write as the from end. + * + * @return self::DIRECTION_FROM|self::DIRECTION_TO + */ + public function writeDirectionFor(CustomField $field): string + { + return $this->is_symmetric + ? self::DIRECTION_FROM + : $this->directionFor($field); + } + + public function isHeadless(): bool + { + return $this->from_field_id === null && $this->to_field_id === null; + } +} diff --git a/src/Models/CustomFieldSection.php b/src/Models/CustomFieldSection.php index b4d06672..aee0b97a 100644 --- a/src/Models/CustomFieldSection.php +++ b/src/Models/CustomFieldSection.php @@ -28,7 +28,6 @@ * @property CustomFieldSectionType $type * @property CustomFieldWidth $width * @property string $entity_type - * @property ?string $lookup_type * @property CustomFieldSectionSettingsData $settings * @property int $sort_order * @property bool $active diff --git a/src/Models/Scopes/ActivableScope.php b/src/Models/Scopes/ActivableScope.php index 0499e828..2eddc23a 100644 --- a/src/Models/Scopes/ActivableScope.php +++ b/src/Models/Scopes/ActivableScope.php @@ -8,6 +8,9 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; +/** + * @implements Scope + */ class ActivableScope implements Scope { /** diff --git a/src/Models/Scopes/CustomFieldsActivableScope.php b/src/Models/Scopes/CustomFieldsActivableScope.php index 507c63ad..23ad33cd 100644 --- a/src/Models/Scopes/CustomFieldsActivableScope.php +++ b/src/Models/Scopes/CustomFieldsActivableScope.php @@ -13,7 +13,7 @@ /** * Custom fields activable scope that also checks section activation. */ -class CustomFieldsActivableScope extends ActivableScope +final class CustomFieldsActivableScope extends ActivableScope { /** * Apply the scope to a given Eloquent query builder. diff --git a/src/Models/Scopes/SortOrderScope.php b/src/Models/Scopes/SortOrderScope.php index eef63cf0..c3abb78a 100644 --- a/src/Models/Scopes/SortOrderScope.php +++ b/src/Models/Scopes/SortOrderScope.php @@ -8,7 +8,10 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; -class SortOrderScope implements Scope +/** + * @implements Scope + */ +final class SortOrderScope implements Scope { /** * @param Builder $builder diff --git a/src/Models/Scopes/TenantScope.php b/src/Models/Scopes/TenantScope.php index 7243a050..63a3654b 100644 --- a/src/Models/Scopes/TenantScope.php +++ b/src/Models/Scopes/TenantScope.php @@ -11,7 +11,10 @@ use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Services\TenantContextService; -class TenantScope implements Scope +/** + * @implements Scope + */ +final class TenantScope implements Scope { /** * @param Builder $builder diff --git a/src/Observers/CustomFieldObserver.php b/src/Observers/CustomFieldObserver.php index e045381d..625ed5df 100644 --- a/src/Observers/CustomFieldObserver.php +++ b/src/Observers/CustomFieldObserver.php @@ -4,14 +4,26 @@ namespace Relaticle\CustomFields\Observers; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\DB; +use Relaticle\CustomFields\CustomFields; +use Relaticle\CustomFields\Facades\CustomFieldsType; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldRelationship; +use Relaticle\CustomFields\Models\Scopes\TenantScope; +use Relaticle\CustomFields\Services\Relationships\DeleteRelationshipDefinition; use Relaticle\CustomFields\Services\Visibility\BackendVisibilityService; +use Relaticle\CustomFields\Support\RelationshipTables; use RuntimeException; -class CustomFieldObserver +final class CustomFieldObserver { /** * Prevent modification of protected attributes on system-defined fields. + * + * A type change is allowed only when the old and new types share the same storage + * representation (e.g. select to status): the row keeps its options and values, only + * how they are interpreted changes. */ public function updating(CustomField $customField): void { @@ -19,9 +31,21 @@ public function updating(CustomField $customField): void return; } - if ($customField->isDirty(['name', 'code', 'type'])) { + if ($customField->isDirty(['name', 'code'])) { throw new RuntimeException('Cannot modify name, code, or type of system-defined fields.'); } + + if ($customField->isDirty('type') && ! $this->isStorageCompatibleTypeChange($customField)) { + throw new RuntimeException('Cannot modify name, code, or type of system-defined fields.'); + } + } + + private function isStorageCompatibleTypeChange(CustomField $customField): bool + { + $originalDataType = CustomFieldsType::getFieldType($customField->getOriginal('type'))?->dataType; + $newDataType = CustomFieldsType::getFieldType($customField->type)?->dataType; + + return $originalDataType !== null && $originalDataType === $newDataType; } /** @@ -32,6 +56,15 @@ public function saved(CustomField $customField): void BackendVisibilityService::clearCache($customField->entity_type); } + /** + * The slots are read while the field row still exists: a host with foreign keys on has + * already had them set to null by the time the delete lands. + */ + public function deleting(CustomField $customField): void + { + $this->unpairRelationshipSlots($customField); + } + public function deleted(CustomField $customField): void { BackendVisibilityService::clearCache($customField->entity_type); @@ -42,4 +75,51 @@ public function deleted(CustomField $customField): void // Delete the custom field values $customField->values()->delete(); } + + /** + * Losing one presentation slot leaves the definition and its edges intact: the partner + * keeps reading them from its own side. The lookup drops the tenant scope because the + * field is already identified, and a foreign context must not strand a slot. + */ + private function unpairRelationshipSlots(CustomField $customField): void + { + if (! RelationshipTables::exist()) { + return; + } + + DB::transaction(function () use ($customField): void { + $definitions = CustomFields::newRelationshipModel() + ->newQuery() + ->withoutGlobalScope(TenantScope::class) + ->where(fn (Builder $query): Builder => $query + ->where('from_field_id', $customField->getKey()) + ->orWhere('to_field_id', $customField->getKey())) + ->get(); + + foreach ($definitions as $definition) { + $this->unpair($definition, $customField); + } + }); + } + + /** + * A definition that keeps no slot at all was never the headless kind, which is created + * without fields, so it leaves with the field that was its last face. + */ + private function unpair(CustomFieldRelationship $definition, CustomField $customField): void + { + $key = (string) $customField->getKey(); + + $fromFieldId = (string) $definition->from_field_id === $key ? null : $definition->from_field_id; + $toFieldId = (string) $definition->to_field_id === $key ? null : $definition->to_field_id; + + $definition->forceFill([ + 'from_field_id' => $fromFieldId, + 'to_field_id' => $toFieldId, + ])->save(); + + if ($fromFieldId === null && $toFieldId === null) { + app(DeleteRelationshipDefinition::class)->execute($definition); + } + } } diff --git a/src/Observers/CustomFieldRelationshipObserver.php b/src/Observers/CustomFieldRelationshipObserver.php new file mode 100644 index 00000000..ed6acf88 --- /dev/null +++ b/src/Observers/CustomFieldRelationshipObserver.php @@ -0,0 +1,24 @@ +isDirty(['from_entity_type', 'to_entity_type', 'is_symmetric'])) { + return; + } + + throw new RuntimeException('Cannot change the entity types or the symmetry of an existing relationship.'); + } +} diff --git a/src/Observers/CustomFieldSectionObserver.php b/src/Observers/CustomFieldSectionObserver.php index 0cd5755d..328a2c4a 100644 --- a/src/Observers/CustomFieldSectionObserver.php +++ b/src/Observers/CustomFieldSectionObserver.php @@ -1,10 +1,12 @@ app->singleton(EntityManagerInterface::class, EntityManager::class); $this->app->singleton(EntityManager::class, function (mixed $app): EntityManager { $config = $this->getEntityConfig(); @@ -126,12 +124,14 @@ private function registerFilters(EntityManager $manager): void /** * Get entity configuration from the builder + * + * @return array */ private function getEntityConfig(): array { $entityConfiguration = config('custom-fields.entity_configuration'); - if ($entityConfiguration instanceof EntityConfigurationInterface) { + if ($entityConfiguration instanceof EntityConfigurator) { return [ 'auto_discover_entities' => $entityConfiguration->getAutoDiscover(), 'entity_discovery_paths' => $entityConfiguration->getDiscoveryPaths(), diff --git a/src/Providers/ImportsServiceProvider.php b/src/Providers/ImportsServiceProvider.php index 6a0d8842..b7da8dfb 100644 --- a/src/Providers/ImportsServiceProvider.php +++ b/src/Providers/ImportsServiceProvider.php @@ -19,7 +19,7 @@ * - Configurator is created when needed * - WeakMap storage is static and self-initializing */ -class ImportsServiceProvider extends ServiceProvider +final class ImportsServiceProvider extends ServiceProvider { /** * Register import services. diff --git a/src/Providers/ValidationServiceProvider.php b/src/Providers/ValidationServiceProvider.php index eda2c3b1..80fc79b4 100644 --- a/src/Providers/ValidationServiceProvider.php +++ b/src/Providers/ValidationServiceProvider.php @@ -8,7 +8,7 @@ use Illuminate\Support\ServiceProvider; use Relaticle\CustomFields\Services\ValidationService; -class ValidationServiceProvider extends ServiceProvider +final class ValidationServiceProvider extends ServiceProvider { public function register(): void { diff --git a/src/QueryBuilders/CustomFieldQueryBuilder.php b/src/QueryBuilders/CustomFieldQueryBuilder.php index 55e370d8..cb50d26a 100644 --- a/src/QueryBuilders/CustomFieldQueryBuilder.php +++ b/src/QueryBuilders/CustomFieldQueryBuilder.php @@ -1,5 +1,7 @@ $query + * @param array> $attributes + * @param ?class-string $resourceClass + * @return Builder + */ + public function apply(Builder $query, string $search, array $attributes, ?string $resourceClass = null): Builder + { + if ($attributes === []) { + return $query; + } + + $connection = $query->getModel()->getConnection(); + $forcedCaseInsensitive = $this->isForcedCaseInsensitive($resourceClass); + $term = generate_search_term_expression($search, $forcedCaseInsensitive, $connection); + + foreach ($this->terms($term, $resourceClass) as $word) { + $query->where(fn (Builder $nested): Builder => $this->matchAnyAttribute($nested, $word, $attributes, $connection, $forcedCaseInsensitive)); + } + + return $query; + } + + /** + * @template TModel of Model + * + * @param Builder $query + * @param array> $attributes + * @return Builder + */ + private function matchAnyAttribute(Builder $query, string $term, array $attributes, Connection $connection, ?bool $forcedCaseInsensitive): Builder + { + foreach ($attributes as $group) { + foreach (Arr::wrap($group) as $attribute) { + $this->matchAttribute($query, $term, $attribute, $connection, $forcedCaseInsensitive); + } + } + + return $query; + } + + /** + * @param Builder $query + */ + private function matchAttribute(Builder $query, string $term, string $attribute, Connection $connection, ?bool $forcedCaseInsensitive): void + { + if (! str_contains($attribute, '.')) { + $query->orWhere( + generate_search_column_expression($query->qualifyColumn($attribute), $forcedCaseInsensitive, $connection), + 'like', + sprintf('%%%s%%', $term), + ); + + return; + } + + $query->orWhereHas( + Str::beforeLast($attribute, '.'), + fn (Builder $related): Builder => $related->where( + generate_search_column_expression($related->qualifyColumn(Str::afterLast($attribute, '.')), $forcedCaseInsensitive, $connection), + 'like', + sprintf('%%%s%%', $term), + ), + ); + } + + /** + * Filament ANDs the words of a split term and ORs the attributes inside each word. + * + * @param ?class-string $resourceClass + * @return array + */ + private function terms(string $term, ?string $resourceClass): array + { + if (! $this->shouldSplit($resourceClass)) { + return [$term]; + } + + $words = str_getcsv( + (string) preg_replace('/(\s|\x{3164}|\x{1160})+/u', ' ', $term), + separator: ' ', + escape: '\\', + ); + + $words = array_values(array_filter($words, static fn (?string $word): bool => filled($word))); + + return $words === [] ? [$term] : $words; + } + + /** + * @param ?class-string $resourceClass + */ + private function isForcedCaseInsensitive(?string $resourceClass): ?bool + { + if ($resourceClass === null || ! method_exists($resourceClass, 'isGlobalSearchForcedCaseInsensitive')) { + return null; + } + + return $resourceClass::isGlobalSearchForcedCaseInsensitive(); + } + + /** + * @param ?class-string $resourceClass + */ + private function shouldSplit(?string $resourceClass): bool + { + if ($resourceClass === null || ! method_exists($resourceClass, 'shouldSplitGlobalSearchTerms')) { + return true; + } + + return $resourceClass::shouldSplitGlobalSearchTerms(); + } +} diff --git a/src/QueryBuilders/RecordLinkQuery.php b/src/QueryBuilders/RecordLinkQuery.php new file mode 100644 index 00000000..b9440e5a --- /dev/null +++ b/src/QueryBuilders/RecordLinkQuery.php @@ -0,0 +1,213 @@ + $query + * @param array $targetIds + * @return Builder + */ + public function whereLinkedTo(Builder $query, CustomFieldRelationship $definition, string $direction, array $targetIds): Builder + { + $host = $query->getModel(); + + return $query->where(function (Builder $outer) use ($definition, $direction, $host, $targetIds): void { + foreach ($this->ends($direction) as $end) { + $outer->orWhereExists(function (QueryBuilder $sub) use ($definition, $host, $end, $targetIds): void { + $this->edge($sub, $definition, $host, $end) + ->whereIn($this->column($this->opposite($end).'_entity_id'), $targetIds); + }); + } + }); + } + + /** + * @template TModel of Model + * + * @param Builder $query + * @param array $searchAttributes + * @return Builder + */ + public function whereLinkedMatching(Builder $query, CustomFieldRelationship $definition, string $direction, array $searchAttributes, string $search): Builder + { + $target = $this->targetModel($definition, $direction); + + if (! $target instanceof Model || $searchAttributes === []) { + return $query; + } + + $host = $query->getModel(); + + // The matching-ids subquery is uncorrelated, so it needs no alias even when the + // host and the target are the same table. + $matches = $this->entitySearch + ->apply($target->newQuery(), $search, $searchAttributes, Filament::getModelResource($target::class)) + ->select($target->qualifyColumn($target->getKeyName())) + ->getQuery(); + + return $query->where(function (Builder $outer) use ($definition, $direction, $host, $matches): void { + foreach ($this->ends($direction) as $end) { + $outer->orWhereExists(function (QueryBuilder $sub) use ($definition, $host, $end, $matches): void { + $this->edge($sub, $definition, $host, $end) + ->whereIn($this->column($this->opposite($end).'_entity_id'), $matches); + }); + } + }); + } + + /** + * Ordering reads the first linked record's primary attribute through two scalar + * subqueries. The target is aliased because a self relationship would otherwise + * shadow the host row the inner query correlates against. + * + * @template TModel of Model + * + * @param Builder $query + * @return Builder + */ + public function orderByLinkedAttribute(Builder $query, CustomFieldRelationship $definition, string $direction, string $attribute, string $sortDirection): Builder + { + $target = $this->targetModel($definition, $direction); + + if (! $target instanceof Model) { + return $query; + } + + $host = $query->getModel(); + $alias = 'custom_field_link_target'; + + $linkedId = DB::table($this->table()) + ->select(new Expression($this->linkedIdExpression($query, $direction, $host))) + ->where($this->column('relationship_id'), $definition->getKey()) + ->whereNull($this->column('active_until')) + ->where(fn (QueryBuilder $nested): QueryBuilder => $this->matchHost($nested, $direction, $host)) + ->orderBy($this->column('sort_order')) + ->limit(1); + + $linkedAttribute = DB::table($target->getTable().' as '.$alias) + ->select($alias.'.'.$attribute) + ->where($alias.'.'.$target->getKeyName(), '=', $linkedId) + ->limit(1); + + $sql = $linkedAttribute->toSql(); + $bindings = $linkedAttribute->getBindings(); + + // Unlinked rows sort last in both directions. The leading term says so without a + // NULLS LAST clause, which the MySQL family does not have. + return $query->orderByRaw( + sprintf('(%s) is null asc, (%s) %s', $sql, $sql, $this->sortDirection($sortDirection)), + [...$bindings, ...$bindings], + ); + } + + private function sortDirection(string $direction): string + { + return strtolower($direction) === 'desc' ? 'desc' : 'asc'; + } + + private function matchHost(QueryBuilder $nested, string $direction, Model $host): QueryBuilder + { + foreach ($this->ends($direction) as $end) { + $nested->orWhere(fn (QueryBuilder $side): QueryBuilder => $side + ->where($this->column($end.'_entity_type'), $host->getMorphClass()) + ->whereColumn($this->column($end.'_entity_id'), $host->qualifyColumn($host->getKeyName()))); + } + + return $nested; + } + + private function edge(QueryBuilder $sub, CustomFieldRelationship $definition, Model $host, string $end): QueryBuilder + { + return $sub->select(new Expression('1')) + ->from($this->table()) + ->where($this->column('relationship_id'), $definition->getKey()) + ->whereNull($this->column('active_until')) + ->where($this->column($end.'_entity_type'), $host->getMorphClass()) + ->whereColumn($this->column($end.'_entity_id'), $host->qualifyColumn($host->getKeyName())); + } + + /** + * @param Builder $query + */ + private function linkedIdExpression(Builder $query, string $direction, Model $host): string + { + $grammar = $query->getQuery()->getGrammar(); + + if ($direction !== CustomFieldRelationship::DIRECTION_BOTH) { + return $grammar->wrap($this->column($this->opposite($direction).'_entity_id')); + } + + return sprintf( + 'case when %s = %s then %s else %s end', + $grammar->wrap($this->column('from_entity_id')), + $grammar->wrap($host->qualifyColumn($host->getKeyName())), + $grammar->wrap($this->column('to_entity_id')), + $grammar->wrap($this->column('from_entity_id')), + ); + } + + private function targetModel(CustomFieldRelationship $definition, string $direction): ?Model + { + $entityType = $direction === CustomFieldRelationship::DIRECTION_TO + ? $definition->from_entity_type + : $definition->to_entity_type; + + $entityClass = Relation::getMorphedModel($entityType) ?? $entityType; + + if (! class_exists($entityClass) || ! is_subclass_of($entityClass, Model::class)) { + return null; + } + + return new $entityClass; + } + + /** + * @return array + */ + private function ends(string $direction): array + { + return match ($direction) { + CustomFieldRelationship::DIRECTION_FROM => [CustomFieldRelationship::DIRECTION_FROM], + CustomFieldRelationship::DIRECTION_TO => [CustomFieldRelationship::DIRECTION_TO], + default => [CustomFieldRelationship::DIRECTION_FROM, CustomFieldRelationship::DIRECTION_TO], + }; + } + + private function opposite(string $end): string + { + return $end === CustomFieldRelationship::DIRECTION_FROM + ? CustomFieldRelationship::DIRECTION_TO + : CustomFieldRelationship::DIRECTION_FROM; + } + + private function column(string $column): string + { + return $this->table().'.'.$column; + } + + private function table(): string + { + return (string) config('custom-fields.database.table_names.custom_field_links'); + } +} diff --git a/src/Rules/CardinalityRule.php b/src/Rules/CardinalityRule.php new file mode 100644 index 00000000..7aabc57b --- /dev/null +++ b/src/Rules/CardinalityRule.php @@ -0,0 +1,47 @@ +customField->relationshipDefinition(); + + if (! $definition instanceof CustomFieldRelationship) { + return; + } + + $payload = RecordLinkPayload::fromValue($value); + + $violations = app(CardinalityGuard::class)->violations( + $definition, + $definition->writeDirectionFor($this->customField), + $this->recordId, + $payload->ids, + $payload->confirmed, + ); + + foreach ($violations as $violation) { + $fail($violation); + } + } +} diff --git a/src/Rules/UniqueCustomFieldValue.php b/src/Rules/UniqueCustomFieldValue.php index 0acf854a..f6398076 100644 --- a/src/Rules/UniqueCustomFieldValue.php +++ b/src/Rules/UniqueCustomFieldValue.php @@ -7,6 +7,7 @@ use Closure; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Arr; use Relaticle\CustomFields\CustomFields; @@ -14,7 +15,9 @@ use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\FieldTypeSystem\FieldManager; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldValue; use Relaticle\CustomFields\Services\TenantContextService; +use RuntimeException; final class UniqueCustomFieldValue implements ValidationRule { @@ -92,12 +95,24 @@ private function findTakenValues(array $normalizedValues): array ->all(); } + /** + * @return Builder + */ private function baseQuery(): Builder { $valueModel = CustomFields::newValueModel(); $entityType = $this->customField->entity_type; $entityClass = Relation::getMorphedModel($entityType) ?? $entityType; + + if (! class_exists($entityClass) || ! is_subclass_of($entityClass, Model::class)) { + throw new RuntimeException(sprintf( + 'Custom field "%s" references an unresolvable entity type "%s".', + $this->customField->code, + $entityType, + )); + } + $morphAlias = (new $entityClass)->getMorphClass(); $query = $valueModel->newQuery() diff --git a/src/Services/ModelAttributeDiscoveryService.php b/src/Services/ModelAttributeDiscoveryService.php index 548a42b6..ddba207f 100644 --- a/src/Services/ModelAttributeDiscoveryService.php +++ b/src/Services/ModelAttributeDiscoveryService.php @@ -6,7 +6,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; use ReflectionClass; use Relaticle\CustomFields\Enums\FieldDataType; @@ -65,16 +64,16 @@ public function getAttributes(string $entityType): Collection $excludedByCast = $this->getCastExcludedColumns($model); $columns = rescue( - fn () => Schema::getColumns($model->getTable()), + fn () => $model->getConnection()->getSchemaBuilder()->getColumns($model->getTable()), [] ); $attributes = collect($columns) ->filter(fn (array $column): bool => $this->shouldIncludeColumn($column, $excludedByCast)) ->mapWithKeys(fn (array $column): array => [ - (string) $column['name'] => [ - 'code' => (string) $column['name'], - 'label' => $this->generateLabel((string) $column['name']), + $column['name'] => [ + 'code' => $column['name'], + 'label' => $this->generateLabel($column['name']), 'data_type' => $this->mapColumnType($column, $model), ], ]); @@ -183,7 +182,7 @@ private function shouldIncludeColumn(array $column, array $castExcluded): bool return false; } - $excludedTypes = ['json', 'binary', 'blob', 'longblob', 'mediumblob']; + $excludedTypes = ['json', 'jsonb', 'binary', 'blob', 'longblob', 'mediumblob']; return ! in_array(strtolower($column['type_name']), $excludedTypes, true); } diff --git a/src/Services/Options/ComponentOptionsExtractor.php b/src/Services/Options/ComponentOptionsExtractor.php index 1c9bc446..28fb9ea6 100644 --- a/src/Services/Options/ComponentOptionsExtractor.php +++ b/src/Services/Options/ComponentOptionsExtractor.php @@ -49,6 +49,8 @@ public function extractOptionsFromFieldType(string $fieldTypeKey, ?CustomField $ /** * Extract options from a closure-based form component + * + * @return array */ private function extractFromClosure(Closure $closure, ?CustomField $field): array { @@ -69,6 +71,8 @@ private function extractFromClosure(Closure $closure, ?CustomField $field): arra /** * Extract options from a component class + * + * @return array */ private function extractFromComponentClass(): array { @@ -79,6 +83,8 @@ private function extractFromComponentClass(): array /** * Extract options from an instantiated Filament component + * + * @return array */ private function extractFromComponent(Field $component): array { @@ -118,6 +124,8 @@ private function extractFromComponent(Field $component): array /** * Extract sample options from a searchable component + * + * @return array */ private function extractFromSearchableComponent(Field $component): array { diff --git a/src/Services/Relationships/AuthenticatedActorResolver.php b/src/Services/Relationships/AuthenticatedActorResolver.php new file mode 100644 index 00000000..a24e62da --- /dev/null +++ b/src/Services/Relationships/AuthenticatedActorResolver.php @@ -0,0 +1,18 @@ +user(); + + return $user instanceof Model ? $user : null; + } +} diff --git a/src/Services/Relationships/CardinalityGuard.php b/src/Services/Relationships/CardinalityGuard.php new file mode 100644 index 00000000..df42f28c --- /dev/null +++ b/src/Services/Relationships/CardinalityGuard.php @@ -0,0 +1,214 @@ + $targetIds + * @param array $confirmed Ids the caller agreed to take from their holder. + * @return array + */ + public function violations( + CustomFieldRelationship $definition, + string $direction, + int|string|null $recordId, + array $targetIds, + array $confirmed = [], + ): array { + $targets = $this->normalize($targetIds); + $messages = []; + + if ($this->endIsSingle($definition, $direction) && count($targets) > 1) { + $messages[] = __('custom-fields::custom-fields.relationships.errors.single_value'); + } + + if ($targets === []) { + return $messages; + } + + if (! $this->endIsSingle($definition, $this->opposite($direction))) { + return $messages; + } + + foreach ($this->holders($definition, $direction, $recordId, $targets) as $targetId => $holderId) { + // The confirmation belongs to the record it was given for, so every other holder + // in the same payload is still reported. + if (in_array((string) $targetId, $confirmed, true)) { + continue; + } + + $messages[] = __('custom-fields::custom-fields.relationships.errors.already_linked', [ + 'record' => $this->label($this->entityType($definition, $this->opposite($direction)), $targetId), + 'holder' => $this->label($this->entityType($definition, $direction), $holderId), + ]); + } + + return $messages; + } + + /** + * The record already on the far end of an edge is the one a write would displace, so a + * record keeping its own link reports nothing. + * + * @param array $targets + * @return array + */ + private function holders(CustomFieldRelationship $definition, string $direction, int|string|null $recordId, array $targets): array + { + $links = $this->activeLinks($definition) + ->where(function (Builder $nested) use ($definition, $direction, $targets): void { + foreach ($this->targetEnds($definition, $direction) as $end) { + $nested->orWhere(fn (Builder $side): Builder => $side + ->where($end.'_entity_type', $this->entityType($definition, $end)) + ->whereIn($end.'_entity_id', $targets)); + } + }) + ->get(); + + $holders = []; + + foreach ($links as $link) { + [$targetId, $holderId] = $this->ends($link, $definition, $direction, $targets); + + if ($targetId === null) { + continue; + } + + if ((string) $holderId === (string) $recordId) { + continue; + } + + $holders[$targetId] ??= $holderId; + } + + return $holders; + } + + /** + * A directional edge is read by end: two entity types share one id space often enough + * that finding the target by value would name the wrong pair. Only a symmetric edge, + * canonicalized by value, has to be read that way. + * + * @param array $targets + * @return array{0: ?string, 1: int|string} + */ + private function ends(CustomFieldLink $link, CustomFieldRelationship $definition, string $direction, array $targets): array + { + if (! $definition->is_symmetric) { + $targetId = (string) $link->{$this->opposite($direction).'_entity_id'}; + + return [ + in_array($targetId, $targets, true) ? $targetId : null, + $link->{$direction.'_entity_id'}, + ]; + } + + $fromId = (string) $link->from_entity_id; + $toId = (string) $link->to_entity_id; + + if (in_array($toId, $targets, true)) { + return [$toId, $link->from_entity_id]; + } + + if (in_array($fromId, $targets, true)) { + return [$fromId, $link->to_entity_id]; + } + + return [null, $toId]; + } + + /** + * A symmetric definition stores its ends least first, so a target can sit on either. + * + * @return array + */ + private function targetEnds(CustomFieldRelationship $definition, string $direction): array + { + if ($definition->is_symmetric) { + return [CustomFieldRelationship::DIRECTION_FROM, CustomFieldRelationship::DIRECTION_TO]; + } + + return [$this->opposite($direction)]; + } + + /** + * @return Builder + */ + private function activeLinks(CustomFieldRelationship $definition): Builder + { + return CustomFields::newLinkModel() + ->newQuery() + ->where('relationship_id', $definition->getKey()) + ->whereNull('active_until'); + } + + private function label(string $entityType, int|string $key): string + { + $entity = Entities::getEntity($entityType); + + if (! $entity instanceof EntityConfigurationData) { + return (string) $key; + } + + $record = $entity->newQuery()->whereKey($key)->first(); + $title = $record?->getAttribute($entity->getPrimaryAttribute()); + + return is_scalar($title) && (string) $title !== '' ? (string) $title : (string) $key; + } + + /** + * Whether a record on the given end holds a single record on the other. The picker asks + * before it offers to move a record, so the offer and the refusal read the same rule. + */ + public function endHoldsOne(CustomFieldRelationship $definition, string $end): bool + { + return $this->endIsSingle($definition, $end); + } + + private function endIsSingle(CustomFieldRelationship $definition, string $end): bool + { + return $end === CustomFieldRelationship::DIRECTION_TO + ? $definition->cardinality->toSideIsSingle() + : $definition->cardinality->fromSideIsSingle(); + } + + private function entityType(CustomFieldRelationship $definition, string $end): string + { + return $end === CustomFieldRelationship::DIRECTION_TO + ? $definition->to_entity_type + : $definition->from_entity_type; + } + + private function opposite(string $end): string + { + return $end === CustomFieldRelationship::DIRECTION_FROM + ? CustomFieldRelationship::DIRECTION_TO + : CustomFieldRelationship::DIRECTION_FROM; + } + + /** + * @param array $targetIds + * @return array + */ + private function normalize(array $targetIds): array + { + return array_values(array_unique(array_map( + static fn (int|string $id): string => (string) $id, + $targetIds, + ))); + } +} diff --git a/src/Services/Relationships/CreateRelationshipDefinition.php b/src/Services/Relationships/CreateRelationshipDefinition.php new file mode 100644 index 00000000..7110a628 --- /dev/null +++ b/src/Services/Relationships/CreateRelationshipDefinition.php @@ -0,0 +1,215 @@ +assertDefinable($data); + + return DB::transaction(function () use ($data): CustomFieldRelationship { + $tenantId = TenantContextService::getCurrentTenantId(); + + $fromField = $data->fromField instanceof FieldSlotData + ? $this->slotField($data->fromField, $data->fromEntityType, $tenantId) + : null; + + $toField = $data->toField instanceof FieldSlotData + ? $this->slotField($data->toField, $data->toEntityType, $tenantId) + : null; + + // A symmetric relationship renders one field that reads both ends, so both slots + // point at it and directionFor() answers 'from' for either direction. + $attributes = [ + 'code' => $data->code, + 'from_entity_type' => $data->fromEntityType, + 'to_entity_type' => $data->toEntityType, + 'cardinality' => $data->cardinality, + 'is_symmetric' => $data->isSymmetric, + 'from_field_id' => $fromField?->getKey(), + 'to_field_id' => $data->isSymmetric ? $fromField?->getKey() : $toField?->getKey(), + ]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $attributes[config('custom-fields.database.column_names.tenant_foreign_key')] = $tenantId; + } + + return CustomFields::newRelationshipModel()->newQuery()->create($attributes); + }); + } + + private function assertDefinable(RelationshipDefinitionData $data): void + { + $this->assertEndResolves($data->fromEntityType, $data->code); + $this->assertEndResolves($data->toEntityType, $data->code); + + if ($data->isSymmetric && $data->fromEntityType !== $data->toEntityType) { + throw new InvalidArgumentException('A symmetric relationship requires matching entity types.'); + } + + if ($data->isSymmetric && $data->toField instanceof FieldSlotData) { + throw new InvalidArgumentException('A symmetric relationship has a single field slot.'); + } + + if ($data->isSymmetric && $data->cardinality->fromSideIsSingle() !== $data->cardinality->toSideIsSingle()) { + throw new InvalidArgumentException(sprintf('A symmetric relationship cannot use the directional cardinality [%s].', $data->cardinality->value)); + } + + if ($this->codeIsTaken($data->code)) { + throw new InvalidArgumentException(sprintf('A relationship with the code [%s] already exists.', $data->code)); + } + + foreach ([$data->fromField, $data->toField] as $slot) { + if ($slot instanceof FieldSlotData) { + $this->assertSlotTypeLinks($slot, $data->code); + } + } + } + + /** + * A slot renders one end, so its field type has to be one that points at records. A slot + * adopting an existing field is checked against that row instead. + */ + private function assertSlotTypeLinks(FieldSlotData $slot, string $code): void + { + if ($slot->fieldId !== null) { + return; + } + + if (CustomFieldsType::getFieldType($slot->type)?->requiresRelationship === true) { + return; + } + + throw new InvalidArgumentException(sprintf('A relationship slot cannot render the [%s] field type (relationship [%s]).', $slot->type, $code)); + } + + /** + * Ends are locked once a definition exists, so an unusable one is rejected here rather + * than at the first write. The resolution mirrors the writer's. + */ + private function assertEndResolves(string $entityType, string $code): void + { + $entityClass = Relation::getMorphedModel($entityType) ?? $entityType; + + if (class_exists($entityClass) && is_subclass_of($entityClass, Model::class)) { + return; + } + + throw new InvalidArgumentException(sprintf('A relationship cannot end on the unresolvable entity type [%s] (relationship [%s]).', $entityType, $code)); + } + + private function codeIsTaken(string $code): bool + { + return CustomFields::newRelationshipModel() + ->newQuery() + ->where('code', $code) + ->exists(); + } + + private function slotField(FieldSlotData $slot, string $entityType, int|string|null $tenantId): CustomField + { + if ($slot->fieldId !== null) { + return $this->adoptSlotField($slot->fieldId, $entityType); + } + + return $this->createSlotField($slot, $entityType, $tenantId); + } + + /** + * A caller that owns the whole field form (the management UI, a preset migration) writes + * the field itself and hands the key over, so the definition wraps that row. + */ + private function adoptSlotField(int|string $fieldId, string $entityType): CustomField + { + $field = CustomFields::newCustomFieldModel() + ->query() + ->withDeactivated() + ->whereKey($fieldId) + ->first(); + + if (! $field instanceof CustomField) { + throw new InvalidArgumentException(sprintf('Field [%s] cannot be a relationship slot: it does not exist.', $fieldId)); + } + + if ($field->typeData?->requiresRelationship !== true) { + throw new InvalidArgumentException(sprintf('Field [%s] cannot be a relationship slot: it is a [%s] field.', $field->code, $field->type)); + } + + if ($field->entity_type !== $entityType) { + throw new InvalidArgumentException(sprintf('Field [%s] belongs to [%s], not to the [%s] end.', $field->code, $field->entity_type, $entityType)); + } + + if ($field->relationshipDefinition() instanceof CustomFieldRelationship) { + throw new InvalidArgumentException(sprintf('Field [%s] already renders a relationship.', $field->code)); + } + + return $field; + } + + private function createSlotField(FieldSlotData $slot, string $entityType, int|string|null $tenantId): CustomField + { + $attributes = [ + 'code' => CodeGenerator::generateUniqueFieldCode($slot->name, $entityType, sectionId: $slot->sectionId), + 'name' => $slot->name, + 'type' => $slot->type, + 'entity_type' => $entityType, + 'active' => true, + ]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_SECTIONS)) { + $attributes['custom_field_section_id'] = $slot->sectionId ?? $this->defaultSection($entityType, $tenantId)->getKey(); + } + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $attributes[config('custom-fields.database.column_names.tenant_foreign_key')] = $tenantId; + } + + return CustomFields::newCustomFieldModel()->newQuery()->create($attributes); + } + + /** + * A sectionless field never renders, because the activable scope asks for a section, so + * a slot the caller placed nowhere lands in the entity's default one. An entity with no + * section at all is the paired-field case: the form has nothing to offer there. + */ + private function defaultSection(string $entityType, int|string|null $tenantId): CustomFieldSection + { + $attributes = ['entity_type' => $entityType, 'code' => self::DEFAULT_SECTION_CODE]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $attributes[(string) config('custom-fields.database.column_names.tenant_foreign_key')] = $tenantId; + } + + return CustomFields::newSectionModel() + ->newQuery() + ->withoutGlobalScope(ActivableScope::class) + ->firstOrCreate($attributes, [ + 'name' => __('custom-fields::custom-fields.section.default_section_name'), + 'type' => CustomFieldSectionType::SECTION, + 'active' => true, + ]); + } +} diff --git a/src/Services/Relationships/DeleteRelationshipDefinition.php b/src/Services/Relationships/DeleteRelationshipDefinition.php new file mode 100644 index 00000000..92a67089 --- /dev/null +++ b/src/Services/Relationships/DeleteRelationshipDefinition.php @@ -0,0 +1,95 @@ +slots($definition); + + // Hosts run without foreign keys often enough (sqlite defaults them off) that the + // cascade cannot be the only thing removing the edges. The definition delete is not + // tenant-scoped either, so neither is this one: no context may strand an edge. + CustomFields::newLinkModel() + ->newQuery() + ->withoutGlobalScope(TenantScope::class) + ->where('relationship_id', $definition->getKey()) + ->delete(); + + $definition->delete(); + + foreach ($slots as [$field, $direction]) { + if ($deleteFields) { + $field->delete(); + + continue; + } + + $this->keepAsOneWay($definition, $field, $direction); + } + }); + } + + /** + * @return array + */ + private function slots(CustomFieldRelationship $definition): array + { + $fields = CustomFields::newCustomFieldModel() + ->query() + ->withDeactivated() + ->whereKey(array_filter([$definition->from_field_id, $definition->to_field_id])) + ->get() + ->keyBy(fn (CustomField $field): string => (string) $field->getKey()); + + $slots = []; + + foreach ([CustomFieldRelationship::DIRECTION_FROM => $definition->from_field_id, CustomFieldRelationship::DIRECTION_TO => $definition->to_field_id] as $direction => $fieldId) { + $field = $fields->get((string) $fieldId); + + if (! $field instanceof CustomField) { + continue; + } + + $slots[(string) $fieldId] ??= [$field, $direction]; + } + + return array_values($slots); + } + + private function keepAsOneWay(CustomFieldRelationship $definition, CustomField $field, string $direction): void + { + $holdsFromSlot = $definition->is_symmetric || $direction === CustomFieldRelationship::DIRECTION_FROM; + $holdsToSlot = $definition->is_symmetric || $direction === CustomFieldRelationship::DIRECTION_TO; + + $attributes = [ + 'code' => CodeGenerator::generateUniqueRelationshipCode($field->code), + 'from_entity_type' => $definition->from_entity_type, + 'to_entity_type' => $definition->to_entity_type, + 'cardinality' => $definition->cardinality, + 'is_symmetric' => $definition->is_symmetric, + 'from_field_id' => $holdsFromSlot ? $field->getKey() : null, + 'to_field_id' => $holdsToSlot ? $field->getKey() : null, + ]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $tenantKey = config('custom-fields.database.column_names.tenant_foreign_key'); + $attributes[$tenantKey] = $definition->{$tenantKey}; + } + + CustomFields::newRelationshipModel()->newQuery()->create($attributes); + } +} diff --git a/src/Services/Relationships/LinkReader.php b/src/Services/Relationships/LinkReader.php new file mode 100644 index 00000000..70615140 --- /dev/null +++ b/src/Services/Relationships/LinkReader.php @@ -0,0 +1,183 @@ + + */ + public function orderedIdsFor(Model $record, CustomFieldRelationship $definition, string $direction): array + { + $ids = array_map( + fn (CustomFieldLink $link): int|string => $this->otherEndId($link, $record, $direction), + $this->orderedLinksFor($record, $definition, $direction), + ); + + return $this->castToTargetKeys($ids, $this->targetEntityType($definition, $direction)); + } + + /** + * The same edges the ids come from, kept whole: provenance lives on the row, so a caller + * that wants to say who linked a record and when reads it here rather than re-querying. + * + * @return array + */ + public function orderedLinksFor(Model $record, CustomFieldRelationship $definition, string $direction): array + { + return $this->links($record, $definition, $direction) + ->sortBy(static fn (CustomFieldLink $link): int => $link->sort_order ?? 0) + ->values() + ->all(); + } + + /** + * @return Collection + */ + private function links(Model $record, CustomFieldRelationship $definition, string $direction): Collection + { + $loaded = $this->loadedLinks($record, $direction); + + if ($loaded instanceof Collection) { + return $loaded->filter(fn (CustomFieldLink $link): bool => $link->active_until === null + && (string) $link->relationship_id === (string) $definition->getKey())->values(); + } + + return $this->query($record, $definition, $direction)->get()->toBase(); + } + + /** + * A table page loads both relations once, so a per-row read must never fall back to SQL. + * + * @return ?Collection + */ + private function loadedLinks(Model $record, string $direction): ?Collection + { + $relations = $this->relationNames($direction); + + foreach ($relations as $relation) { + if (! $record->relationLoaded($relation)) { + return null; + } + } + + $links = new Collection; + + foreach ($relations as $relation) { + /** @var iterable $related */ + $related = $record->getRelation($relation); + + foreach ($related as $link) { + // A symmetric edge whose ends are the same record sits in both relations, + // and the SQL path returns that row once. + $links->put((string) $link->getKey(), $link); + } + } + + return $links->values(); + } + + /** + * @return array + */ + private function relationNames(string $direction): array + { + return match ($direction) { + CustomFieldRelationship::DIRECTION_FROM => ['outgoingLinks'], + CustomFieldRelationship::DIRECTION_TO => ['incomingLinks'], + default => ['outgoingLinks', 'incomingLinks'], + }; + } + + /** + * @return Builder + */ + private function query(Model $record, CustomFieldRelationship $definition, string $direction): Builder + { + $query = CustomFields::newLinkModel() + ->newQuery() + ->where('relationship_id', $definition->getKey()) + ->whereNull('active_until') + ->orderBy('sort_order'); + + return $query->where(function (Builder $nested) use ($record, $direction): void { + foreach ($this->ends($direction) as $end) { + $nested->orWhere(fn (Builder $side): Builder => $side + ->where($end.'_entity_type', $record->getMorphClass()) + ->where($end.'_entity_id', $record->getKey())); + } + }); + } + + /** + * @return array + */ + private function ends(string $direction): array + { + return match ($direction) { + CustomFieldRelationship::DIRECTION_FROM => [CustomFieldRelationship::DIRECTION_FROM], + CustomFieldRelationship::DIRECTION_TO => [CustomFieldRelationship::DIRECTION_TO], + default => [CustomFieldRelationship::DIRECTION_FROM, CustomFieldRelationship::DIRECTION_TO], + }; + } + + /** + * The record on the far end of an edge, from the point of view of the one that holds it. + */ + public function otherEndId(CustomFieldLink $link, Model $record, string $direction): int|string + { + if ($direction === CustomFieldRelationship::DIRECTION_FROM) { + return $link->to_entity_id; + } + + if ($direction === CustomFieldRelationship::DIRECTION_TO) { + return $link->from_entity_id; + } + + $recordIsFromEnd = $link->from_entity_type === $record->getMorphClass() + && (string) $link->from_entity_id === (string) $record->getKey(); + + return $recordIsFromEnd ? $link->to_entity_id : $link->from_entity_id; + } + + private function targetEntityType(CustomFieldRelationship $definition, string $direction): string + { + return $direction === CustomFieldRelationship::DIRECTION_TO + ? $definition->from_entity_type + : $definition->to_entity_type; + } + + /** + * Morph ids come back as strings on some drivers, and every consumer compares them + * against real model keys, so they take the target's own key type. + * + * @param array $ids + * @return array + */ + private function castToTargetKeys(array $ids, string $entityType): array + { + $entityClass = Relation::getMorphedModel($entityType) ?? $entityType; + + if (! class_exists($entityClass) || ! is_subclass_of($entityClass, Model::class)) { + return $ids; + } + + if ((new $entityClass)->getKeyType() !== 'int') { + return array_map(strval(...), $ids); + } + + return array_map(intval(...), $ids); + } +} diff --git a/src/Services/Relationships/LinkWriter.php b/src/Services/Relationships/LinkWriter.php new file mode 100644 index 00000000..0df17726 --- /dev/null +++ b/src/Services/Relationships/LinkWriter.php @@ -0,0 +1,448 @@ + $targetIds + * @param array $confirmed Ids the caller agreed to take from their holder. + * + * @throws ValidationException + */ + public function apply(Model $record, CustomField $field, array $targetIds, string $source = CustomFieldLink::SOURCE_USER, array $confirmed = []): void + { + $definition = $field->relationshipDefinition(); + + if (! $definition instanceof CustomFieldRelationship) { + throw new InvalidArgumentException(sprintf('Record field [%s] has no relationship definition.', $field->code)); + } + + try { + DB::transaction(function () use ($record, $definition, $field, $targetIds, $source, $confirmed): void { + $events = $this->diff($record, $definition, $field, $targetIds, $source, $confirmed); + + // A rolled back write never happened, so its listeners must never hear about it. + DB::afterCommit(static function () use ($events): void { + foreach ($events as $event) { + event($event); + } + }); + }); + } catch (UniqueConstraintViolationException $uniqueConstraintViolationException) { + if (! $this->isDuplicateActiveEdge($uniqueConstraintViolationException)) { + throw $uniqueConstraintViolationException; + } + + // Another writer took the edge between our read and our insert. + throw ValidationException::withMessages([ + $field->getFieldName() => __('custom-fields::custom-fields.relationships.errors.conflict'), + ]); + } + } + + /** + * Only the duplicate-edge index becomes a field error. Anything else a listener or a host + * hook violated inside the transaction stays the exception it was. Postgres names the + * index; SQLite names the columns, so there the statement's table identifies it, the edge + * ledger carrying no second unique key. + */ + private function isDuplicateActiveEdge(UniqueConstraintViolationException $exception): bool + { + if (str_contains(strtolower($exception->getMessage()), CustomFieldLink::ACTIVE_EDGE_INDEX)) { + return true; + } + + return str_contains(strtolower($exception->getSql()), strtolower(CustomFields::newLinkModel()->getTable())); + } + + /** + * @param array $targetIds + * @param array $confirmed + * @return array + */ + private function diff(Model $record, CustomFieldRelationship $definition, CustomField $field, array $targetIds, string $source, array $confirmed): array + { + $definition = $this->lock($definition); + + $direction = $definition->writeDirectionFor($field); + + $this->assertRecordSitsOnEnd($record, $definition, $direction); + + $targets = $this->normalize($targetIds); + $current = $this->activeLinksFor($definition, $record->getMorphClass(), (string) $record->getKey(), $direction)->get(); + + // An edge the payload keeps is never touched (spec 2.1), so only the ids being added + // are held to reachability: a target soft-deleted after it was linked would otherwise + // make its own record unsaveable. + $added = array_values(array_diff($targets, $current->map(fn (CustomFieldLink $link): string => $this->otherEndId($link, $record))->all())); + + $this->assertTargetsExist($definition, $field, $direction, $added); + $this->assertCardinality($definition, $field, $direction, $record, $targets, $confirmed); + + $now = now(); + $actor = $this->actorResolver->resolve(); + + $events = []; + + foreach ($current as $link) { + if (! in_array($this->otherEndId($link, $record), $targets, true)) { + $events[] = $this->close($link, $now); + } + } + + foreach ($targets as $index => $targetId) { + $kept = $current->first(fn (CustomFieldLink $link): bool => $link->active_until === null + && $this->otherEndId($link, $record) === $targetId); + + if ($kept instanceof CustomFieldLink) { + $this->reorder($kept, $index); + + continue; + } + + $events = [ + ...$events, + ...$this->closeDisplaced($definition, $record, $direction, $targetId, $now), + new RelationshipLinkCreated($this->insert($definition, $record, $direction, $targetId, $index, $now, $actor, $source)), + ]; + } + + return $events; + } + + /** + * Per-end exclusivity cannot be a static index, so writers serialize on the definition row, + * which always exists. The stored row decides, never the memoised one, and many to many + * skips the lock: there the duplicate-edge index is the whole wall (spec 1.2). + */ + private function lock(CustomFieldRelationship $definition): CustomFieldRelationship + { + $stored = $this->findDefinition($definition->getKey(), locked: false); + + if ($stored->cardinality === RelationshipCardinality::ManyToMany) { + return $stored; + } + + return $this->findDefinition($definition->getKey(), locked: true); + } + + /** + * The key is the identity, so the read drops the tenant scope: a definition reached from a + * cross-tenant context must lock or fail, never fall back to an unlocked copy. + */ + private function findDefinition(int|string $key, bool $locked): CustomFieldRelationship + { + $query = CustomFields::newRelationshipModel() + ->newQuery() + ->withoutGlobalScope(TenantScope::class) + ->whereKey($key); + + if ($locked) { + $query->lockForUpdate(); + } + + return $query->first() ?? throw RelationshipDefinitionDoesNotExistException::whenLinking($key); + } + + /** + * The definition is locked by now, so what the guard reads is what the write would + * displace. The validation layer says the same thing earlier, where a caller can still + * confirm the replacement. + * + * @param array $targets + * @param array $confirmed + * + * @throws ValidationException + */ + private function assertCardinality(CustomFieldRelationship $definition, CustomField $field, string $direction, Model $record, array $targets, array $confirmed): void + { + $violations = $this->cardinality->violations($definition, $direction, $record->getKey(), $targets, $confirmed); + + if ($violations === []) { + return; + } + + throw ValidationException::withMessages([$field->getFieldName() => $violations]); + } + + private function assertRecordSitsOnEnd(Model $record, CustomFieldRelationship $definition, string $direction): void + { + $expected = $direction === CustomFieldRelationship::DIRECTION_FROM + ? $definition->from_entity_type + : $definition->to_entity_type; + + if ($record->getMorphClass() === $expected) { + return; + } + + throw new InvalidArgumentException(sprintf( + 'Record [%s] does not sit on the [%s] end of relationship [%s].', + $record->getMorphClass(), + $direction, + $definition->code, + )); + } + + /** + * @param array $targetIds + * @return array + */ + private function normalize(array $targetIds): array + { + return array_values(array_unique(array_map( + static fn (int|string $id): string => (string) $id, + $targetIds, + ))); + } + + /** + * The target model's own query decides what is reachable, so a host's tenant scope and + * its soft deletes rule out foreign ids before any edge is written. + * + * @param array $targets + */ + private function assertTargetsExist(CustomFieldRelationship $definition, CustomField $field, string $direction, array $targets): void + { + if ($targets === []) { + return; + } + + $entityType = $this->targetEntityType($definition, $direction); + $entityClass = Relation::getMorphedModel($entityType) ?? $entityType; + + if (! class_exists($entityClass) || ! is_subclass_of($entityClass, Model::class)) { + throw new RuntimeException(sprintf( + 'Relationship "%s" references an unresolvable entity type "%s".', + $definition->code, + $entityType, + )); + } + + $target = new $entityClass; + + $reachable = $target->newQuery() + ->whereKey($targets) + ->pluck($target->getKeyName()) + ->map(static fn (mixed $key): string => (string) $key) + ->all(); + + if (array_diff($targets, $reachable) === []) { + return; + } + + throw ValidationException::withMessages([ + $field->getFieldName() => __('custom-fields::custom-fields.relationships.errors.unknown_target'), + ]); + } + + /** + * @return Builder + */ + private function activeLinksFor(CustomFieldRelationship $definition, string $entityType, string $entityId, string $direction): Builder + { + $query = CustomFields::newLinkModel() + ->newQuery() + ->where('relationship_id', $definition->getKey()) + ->whereNull('active_until') + ->orderBy('sort_order'); + + if (! $definition->is_symmetric) { + return $this->matchEnd($query, $entityType, $entityId, $direction); + } + + return $query->where(function (Builder $nested) use ($entityType, $entityId): void { + $nested + ->where(fn (Builder $end): Builder => $this->matchEnd($end, $entityType, $entityId, CustomFieldRelationship::DIRECTION_FROM)) + ->orWhere(fn (Builder $end): Builder => $this->matchEnd($end, $entityType, $entityId, CustomFieldRelationship::DIRECTION_TO)); + }); + } + + /** + * @param Builder $query + * @return Builder + */ + private function matchEnd(Builder $query, string $entityType, string $entityId, string $direction): Builder + { + return $query + ->where($direction.'_entity_type', $entityType) + ->where($direction.'_entity_id', $entityId); + } + + private function otherEndId(CustomFieldLink $link, Model $record): string + { + $recordIsFromEnd = $link->from_entity_type === $record->getMorphClass() + && (string) $link->from_entity_id === (string) $record->getKey(); + + return $recordIsFromEnd + ? (string) $link->to_entity_id + : (string) $link->from_entity_id; + } + + /** + * A record landing in a taken single end replaces what is there: the displaced edge is + * closed, never deleted, so the history keeps it. + * + * @return array + */ + private function closeDisplaced(CustomFieldRelationship $definition, Model $record, string $direction, string $targetId, Carbon $now): array + { + $cardinality = $definition->cardinality; + $targetType = $this->targetEntityType($definition, $direction); + + if ($definition->is_symmetric) { + if (! $cardinality->fromSideIsSingle()) { + return []; + } + + return [ + ...$this->closeAll($this->activeLinksFor($definition, $record->getMorphClass(), (string) $record->getKey(), $direction), $now), + ...$this->closeAll($this->activeLinksFor($definition, $targetType, $targetId, $direction), $now), + ]; + } + + $recordEndIsSingle = $direction === CustomFieldRelationship::DIRECTION_FROM + ? $cardinality->fromSideIsSingle() + : $cardinality->toSideIsSingle(); + + $targetEndIsSingle = $direction === CustomFieldRelationship::DIRECTION_FROM + ? $cardinality->toSideIsSingle() + : $cardinality->fromSideIsSingle(); + + $events = []; + + if ($recordEndIsSingle) { + $events = $this->closeAll($this->activeLinksFor($definition, $record->getMorphClass(), (string) $record->getKey(), $direction), $now); + } + + if ($targetEndIsSingle) { + return [ + ...$events, + ...$this->closeAll($this->activeLinksFor($definition, $targetType, $targetId, $this->opposite($direction)), $now), + ]; + } + + return $events; + } + + /** + * @param Builder $query + * @return array + */ + private function closeAll(Builder $query, Carbon $now): array + { + $events = []; + + foreach ($query->get() as $link) { + $events[] = $this->close($link, $now); + } + + return $events; + } + + private function close(CustomFieldLink $link, Carbon $now): RelationshipLinkClosed + { + $link->close($now); + + return new RelationshipLinkClosed($link); + } + + private function reorder(CustomFieldLink $link, int $index): void + { + if ($link->sort_order === $index) { + return; + } + + $link->sort_order = $index; + $link->save(); + } + + private function insert(CustomFieldRelationship $definition, Model $record, string $direction, string $targetId, int $index, Carbon $now, ?Model $actor, string $source): CustomFieldLink + { + [$fromId, $toId] = $this->ends($definition, $record, $direction, $targetId); + + $attributes = [ + 'relationship_id' => $definition->getKey(), + 'from_entity_type' => $definition->from_entity_type, + 'from_entity_id' => $fromId, + 'to_entity_type' => $definition->to_entity_type, + 'to_entity_id' => $toId, + 'sort_order' => $index, + 'active_from' => $now, + 'created_by_type' => $actor?->getMorphClass(), + 'created_by_id' => $actor?->getKey(), + 'source' => $source, + ]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $tenantKey = config('custom-fields.database.column_names.tenant_foreign_key'); + $attributes[$tenantKey] = $definition->{$tenantKey}; + } + + return CustomFields::newLinkModel()->newQuery()->create($attributes); + } + + /** + * @return array{0: string, 1: string} + */ + private function ends(CustomFieldRelationship $definition, Model $record, string $direction, string $targetId): array + { + $recordId = (string) $record->getKey(); + + // One symmetric row is read from both sides, so its ends are stored least first. + if ($definition->is_symmetric) { + return strcmp($recordId, $targetId) <= 0 + ? [$recordId, $targetId] + : [$targetId, $recordId]; + } + + return $direction === CustomFieldRelationship::DIRECTION_FROM + ? [$recordId, $targetId] + : [$targetId, $recordId]; + } + + private function targetEntityType(CustomFieldRelationship $definition, string $direction): string + { + return $direction === CustomFieldRelationship::DIRECTION_FROM + ? $definition->to_entity_type + : $definition->from_entity_type; + } + + private function opposite(string $direction): string + { + return $direction === CustomFieldRelationship::DIRECTION_FROM + ? CustomFieldRelationship::DIRECTION_TO + : CustomFieldRelationship::DIRECTION_FROM; + } +} diff --git a/src/Services/Relationships/MissingRelationshipDefinitions.php b/src/Services/Relationships/MissingRelationshipDefinitions.php new file mode 100644 index 00000000..447a84ed --- /dev/null +++ b/src/Services/Relationships/MissingRelationshipDefinitions.php @@ -0,0 +1,32 @@ + */ + private array $reported = []; + + public function report(CustomField $field): void + { + $key = (string) $field->getKey(); + + if (isset($this->reported[$key])) { + return; + } + + $this->reported[$key] = true; + + report(RelationshipDefinitionDoesNotExistException::forField($field->code)); + } +} diff --git a/src/Services/Relationships/UpdateRelationshipDefinition.php b/src/Services/Relationships/UpdateRelationshipDefinition.php new file mode 100644 index 00000000..d7a37c4a --- /dev/null +++ b/src/Services/Relationships/UpdateRelationshipDefinition.php @@ -0,0 +1,150 @@ +lock($definition->getKey()); + + if ($locked->cardinality === $cardinality) { + return $locked; + } + + $narrowed = $this->narrowedEnds($locked->cardinality, $cardinality); + + if ($narrowed !== [] && ! $keepFirst) { + throw ValidationException::withMessages([ + 'cardinality' => __('custom-fields::custom-fields.relationships.errors.keep_first_required'), + ]); + } + + $events = $narrowed === [] ? [] : $this->closeSurplus($locked, $narrowed, now()); + + $locked->update(['cardinality' => $cardinality]); + + DB::afterCommit(static function () use ($events): void { + foreach ($events as $event) { + event($event); + } + }); + + return $locked; + }); + } + + /** + * Writers serialize on the definition row, so the narrowing that decides which edges + * survive takes the same lock rather than racing them. + */ + private function lock(int|string $key): CustomFieldRelationship + { + return CustomFields::newRelationshipModel() + ->newQuery() + ->withoutGlobalScope(TenantScope::class) + ->whereKey($key) + ->lockForUpdate() + ->first() ?? throw RelationshipDefinitionDoesNotExistException::whenLinking($key); + } + + /** + * The ends that go from holding many edges to holding one. + * + * @return array + */ + private function narrowedEnds(RelationshipCardinality $from, RelationshipCardinality $to): array + { + $ends = []; + + if (! $from->fromSideIsSingle() && $to->fromSideIsSingle()) { + $ends[] = CustomFieldRelationship::DIRECTION_FROM; + } + + if (! $from->toSideIsSingle() && $to->toSideIsSingle()) { + $ends[] = CustomFieldRelationship::DIRECTION_TO; + } + + return $ends; + } + + /** + * The first edge a record holds on a narrowed end is the one it keeps, in the order the + * lists were written. The rest are closed, never deleted, so the history keeps them. + * + * @param array $ends + * @return array + */ + private function closeSurplus(CustomFieldRelationship $definition, array $ends, Carbon $now): array + { + $taken = []; + $events = []; + + $links = CustomFields::newLinkModel() + ->newQuery() + ->withoutGlobalScope(TenantScope::class) + ->where('relationship_id', $definition->getKey()) + ->whereNull('active_until') + ->orderBy('sort_order') + ->orderBy('id') + ->get(); + + foreach ($links as $link) { + $holders = $this->holders($definition, $link, $ends); + + if (array_intersect($holders, $taken) !== []) { + $link->close($now); + $events[] = new RelationshipLinkClosed($link); + + continue; + } + + $taken = [...$taken, ...$holders]; + } + + return $events; + } + + /** + * A directional edge holds its ends apart, so a self relation counts a record once per + * end: the record something reports to is not the record reporting to it. Only a + * symmetric edge, canonicalized by value, is counted by value (CardinalityGuard::ends()). + * + * @param array $ends + * @return array + */ + private function holders(CustomFieldRelationship $definition, CustomFieldLink $link, array $ends): array + { + $holders = []; + + foreach ($ends as $end) { + $holders[] = $definition->is_symmetric + ? sprintf('%s:%s', $link->{$end.'_entity_type'}, $link->{$end.'_entity_id'}) + : sprintf('%s:%s:%s', $end, $link->{$end.'_entity_type'}, $link->{$end.'_entity_id'}); + } + + return $holders; + } +} diff --git a/src/Services/ValidationService.php b/src/Services/ValidationService.php index 16c3d1c2..2b03ae71 100644 --- a/src/Services/ValidationService.php +++ b/src/Services/ValidationService.php @@ -4,12 +4,14 @@ namespace Relaticle\CustomFields\Services; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; use Relaticle\CustomFields\Enums\FieldDataType; use Relaticle\CustomFields\Facades\CustomFieldsType; use Relaticle\CustomFields\FieldTypeSystem\FieldManager; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldRelationship; use Relaticle\CustomFields\Models\CustomFieldValue; +use Relaticle\CustomFields\Rules\CardinalityRule; use Relaticle\CustomFields\Rules\UniqueCustomFieldValue; use Relaticle\CustomFields\Support\DatabaseFieldConstraints; @@ -115,7 +117,7 @@ private function getCapabilityRules(CustomField $customField): array $rules = []; foreach ($capabilities as $capabilityClass) { - /** @var ValidationCapability $capability */ + /** @var ValidationCapabilityInterface $capability */ $capability = app($capabilityClass); /** @phpstan-ignore nullsafe.neverNull */ $value = $validationRules?->get($capability->key()); @@ -235,6 +237,12 @@ private function getTypeSpecificRules(CustomField $customField, string|int|null $rules[] = new UniqueCustomFieldValue($customField, $ignoreEntityId); } + // Cardinality is what a relationship slot may hold, so it reaches every path that + // validates a payload: the panel form, imports, and the API. + if ($customField->relationshipDefinition() instanceof CustomFieldRelationship) { + $rules[] = new CardinalityRule($customField, $ignoreEntityId); + } + // Currency fields: enforce decimal places from settings if ($customField->type === 'currency') { $decimalPlaces = $customField->getDecimalPlaces(); diff --git a/src/Services/ValueResolver/LookupAttributeResolver.php b/src/Services/ValueResolver/LookupAttributeResolver.php index 0a473a0c..ea0699aa 100644 --- a/src/Services/ValueResolver/LookupAttributeResolver.php +++ b/src/Services/ValueResolver/LookupAttributeResolver.php @@ -11,8 +11,8 @@ use RuntimeException; /** - * Resolves the (lookup model instance, title attribute) pair for a given - * lookup_type, by consulting Filament's registered resource for the model. + * Resolves the (lookup model instance, title attribute) pair for a given target entity + * type, by consulting Filament's registered resource for the model. * * Centralized here so LookupResolver and LookupPreloader share exactly one * source of truth — previously each class had its own copy of this logic. diff --git a/src/Services/ValueResolver/LookupCache.php b/src/Services/ValueResolver/LookupCache.php index 46f18a36..a9f92d4e 100644 --- a/src/Services/ValueResolver/LookupCache.php +++ b/src/Services/ValueResolver/LookupCache.php @@ -5,7 +5,7 @@ namespace Relaticle\CustomFields\Services\ValueResolver; /** - * Request-scoped cache of resolved lookup titles keyed by (lookup_type, id). + * Request-scoped cache of resolved lookup titles keyed by (target entity type, id). * * Populated either lazily by LookupResolver on demand, or eagerly by the * scopeWithCustomFieldValues afterQuery hook. Either way, downstream column diff --git a/src/Services/ValueResolver/LookupMultiValueResolver.php b/src/Services/ValueResolver/LookupMultiValueResolver.php index c17036b6..c1eb62a8 100644 --- a/src/Services/ValueResolver/LookupMultiValueResolver.php +++ b/src/Services/ValueResolver/LookupMultiValueResolver.php @@ -4,12 +4,12 @@ namespace Relaticle\CustomFields\Services\ValueResolver; -use Relaticle\CustomFields\Contracts\ValueResolvers; +use Relaticle\CustomFields\Contracts\ValueResolverInterface; use Relaticle\CustomFields\Models\Contracts\HasCustomFields; use Relaticle\CustomFields\Models\CustomField; use Throwable; -final readonly class LookupMultiValueResolver implements ValueResolvers +final readonly class LookupMultiValueResolver implements ValueResolverInterface { public function __construct(private LookupResolver $lookupResolver) {} diff --git a/src/Services/ValueResolver/LookupPreloader.php b/src/Services/ValueResolver/LookupPreloader.php index 6fe2814e..3b3eb2c6 100644 --- a/src/Services/ValueResolver/LookupPreloader.php +++ b/src/Services/ValueResolver/LookupPreloader.php @@ -9,11 +9,12 @@ use Illuminate\Database\Eloquent\Model; use Relaticle\CustomFields\Models\Contracts\HasCustomFields; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldLink; use Relaticle\CustomFields\Models\CustomFieldValue; /** * Scans a set of loaded host records for their custom-field lookup references - * and primes the LookupCache with one query per lookup_type. + * and primes the LookupCache with one query per target entity type. * * Called by scopeWithCustomFieldValues's afterQuery hook so tables and * infolists get batched lookup resolution for free. @@ -41,6 +42,12 @@ public function preload(EloquentCollection $records): void continue; } + foreach ($this->linkedEnds($record) as $linkedType => $linkedIds) { + foreach ($linkedIds as $linkedId) { + $idsByLookupType[$linkedType][] = $linkedId; + } + } + if (! $record->relationLoaded('customFieldValues')) { continue; } @@ -55,12 +62,14 @@ public function preload(EloquentCollection $records): void continue; } - if ($field->lookup_type === null) { + $lookupType = $field->targetEntityType(); + + if ($lookupType === null) { continue; } foreach ($this->scalarIdsFromValue($value->getValue()) as $id) { - $idsByLookupType[$field->lookup_type][] = $id; + $idsByLookupType[$lookupType][] = $id; } } } @@ -84,6 +93,35 @@ public function preload(EloquentCollection $records): void } } + /** + * Record fields keep no value row, so their titles come off the loaded edges instead. + * + * @return array> + */ + private function linkedEnds(Model $record): array + { + $ends = []; + + foreach (['outgoingLinks' => 'to', 'incomingLinks' => 'from'] as $relation => $end) { + if (! $record->relationLoaded($relation)) { + continue; + } + + /** @var iterable $links */ + $links = $record->getRelation($relation); + + foreach ($links as $link) { + if ($link->active_until !== null) { + continue; + } + + $ends[$link->{$end.'_entity_type'}][] = $link->{$end.'_entity_id'}; + } + } + + return $ends; + } + /** * @return array */ diff --git a/src/Services/ValueResolver/LookupResolver.php b/src/Services/ValueResolver/LookupResolver.php index 464cc8f1..da1ac0a4 100644 --- a/src/Services/ValueResolver/LookupResolver.php +++ b/src/Services/ValueResolver/LookupResolver.php @@ -34,11 +34,13 @@ public function resolveLookupValues(array $values, CustomField $customField): Co return collect($values); } - if ($customField->lookup_type === null) { + $lookupType = $customField->targetEntityType(); + + if ($lookupType === null) { return $customField->options->whereIn('id', $values)->pluck('name'); } - return $this->resolveAgainstLookupModel($customField->lookup_type, $values); + return $this->resolveAgainstLookupModel($lookupType, $values); } /** diff --git a/src/Services/ValueResolver/LookupSingleValueResolver.php b/src/Services/ValueResolver/LookupSingleValueResolver.php index cd443672..f01423a2 100644 --- a/src/Services/ValueResolver/LookupSingleValueResolver.php +++ b/src/Services/ValueResolver/LookupSingleValueResolver.php @@ -4,11 +4,11 @@ namespace Relaticle\CustomFields\Services\ValueResolver; -use Relaticle\CustomFields\Contracts\ValueResolvers; +use Relaticle\CustomFields\Contracts\ValueResolverInterface; use Relaticle\CustomFields\Models\Contracts\HasCustomFields; use Relaticle\CustomFields\Models\CustomField; -final readonly class LookupSingleValueResolver implements ValueResolvers +final readonly class LookupSingleValueResolver implements ValueResolverInterface { public function __construct(private LookupResolver $lookupResolver) {} diff --git a/src/Services/ValueResolver/ValueResolver.php b/src/Services/ValueResolver/ValueResolver.php index d63cad78..24240771 100644 --- a/src/Services/ValueResolver/ValueResolver.php +++ b/src/Services/ValueResolver/ValueResolver.php @@ -4,11 +4,11 @@ namespace Relaticle\CustomFields\Services\ValueResolver; -use Relaticle\CustomFields\Contracts\ValueResolvers; +use Relaticle\CustomFields\Contracts\ValueResolverInterface; use Relaticle\CustomFields\Models\Contracts\HasCustomFields; use Relaticle\CustomFields\Models\CustomField; -readonly class ValueResolver implements ValueResolvers +final readonly class ValueResolver implements ValueResolverInterface { public function __construct( private LookupMultiValueResolver $multiValueResolver, diff --git a/src/Services/Visibility/BackendVisibilityService.php b/src/Services/Visibility/BackendVisibilityService.php index a1b41846..99bb2eb8 100644 --- a/src/Services/Visibility/BackendVisibilityService.php +++ b/src/Services/Visibility/BackendVisibilityService.php @@ -295,9 +295,10 @@ public function getFieldOptions( return $this->normalizeOptionsForVisibility($options); } - // Priority 2: Handle lookup types (existing functionality) - if ($field->lookup_type) { - return $this->getLookupOptions($field->lookup_type); + $targetEntityType = $field->targetEntityType(); + + if ($targetEntityType !== null) { + return $this->getLookupOptions($targetEntityType); } // Priority 3: Fallback to database options (existing functionality) diff --git a/src/Services/Visibility/FrontendVisibilityService.php b/src/Services/Visibility/FrontendVisibilityService.php index e868d0b6..2710ef1f 100644 --- a/src/Services/Visibility/FrontendVisibilityService.php +++ b/src/Services/Visibility/FrontendVisibilityService.php @@ -5,13 +5,11 @@ namespace Relaticle\CustomFields\Services\Visibility; use Illuminate\Support\Collection; -use Illuminate\Support\Str; use Relaticle\CustomFields\Data\VisibilityConditionData; use Relaticle\CustomFields\Data\VisibilityData; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\Enums\VisibilityLogic; use Relaticle\CustomFields\Enums\VisibilityMode; -use Relaticle\CustomFields\Enums\VisibilityOperator; use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Models\CustomField; use Relaticle\CustomFields\Models\CustomFieldSection; @@ -31,6 +29,7 @@ { public function __construct( private CoreVisibilityLogicService $coreLogic, + private JsExpressionGenerator $jsExpressions, ) {} /** @@ -234,7 +233,7 @@ private function buildCondition( ? sprintf("\$get('%s')", $escapedCode) : sprintf("\$get('custom_fields.%s')", $escapedCode); - $expression = $this->buildOperatorExpression( + $expression = $this->jsExpressions->buildOperatorExpression( $condition->operator, $fieldValue, $condition->value, @@ -249,404 +248,6 @@ private function buildCondition( return $mode === VisibilityMode::SHOW_WHEN ? $expression : sprintf('!(%s)', $expression); } - /** - * Build operator expression using the same logic as backend evaluation. - */ - private function buildOperatorExpression( - VisibilityOperator $operator, - string $fieldValue, - mixed $value, - ?CustomField $targetField - ): ?string { - // Validate operator compatibility using core logic - if ( - $targetField instanceof CustomField && - ! $this->coreLogic->isOperatorCompatible($operator, $targetField) - ) { - return null; - } - - return match ($operator) { - VisibilityOperator::EQUALS => $this->buildEqualsExpression( - $fieldValue, - $value, - $targetField - ), - VisibilityOperator::NOT_EQUALS => $this->buildNotEqualsExpression( - $fieldValue, - $value, - $targetField - ), - VisibilityOperator::CONTAINS => $this->buildContainsExpression( - $fieldValue, - $value, - $targetField - ), - VisibilityOperator::NOT_CONTAINS => transform( - $this->buildContainsExpression( - $fieldValue, - $value, - $targetField - ), - fn (?string $expr): string => sprintf('!(%s)', $expr) - ), - VisibilityOperator::GREATER_THAN => $this->buildNumericComparison( - $fieldValue, - $value, - '>' - ), - VisibilityOperator::LESS_THAN => $this->buildNumericComparison( - $fieldValue, - $value, - '<' - ), - VisibilityOperator::IS_EMPTY => $this->buildEmptyExpression( - $fieldValue, - true - ), - VisibilityOperator::IS_NOT_EMPTY => $this->buildEmptyExpression( - $fieldValue, - false - ), - // IS_IN / IS_NOT_IN are relation-only operators evaluated server-side. - // The set-level guard already returns null for relation conditions; this - // arm keeps the match exhaustive and emits no JS if one ever leaks through. - VisibilityOperator::IS_IN, VisibilityOperator::IS_NOT_IN => null, - }; - } - - /** - * Build equals expression with optionable field support. - */ - private function buildEqualsExpression( - string $fieldValue, - mixed $value, - ?CustomField $targetField - ): string { - if (! $targetField instanceof CustomField || ! $targetField->isChoiceField()) { - return $this->buildStandardEqualsExpression($fieldValue, $value); - } - - return $this->buildOptionExpression($fieldValue, $value, $targetField, 'equals'); - } - - /** - * Build not equals expression. - */ - private function buildNotEqualsExpression( - string $fieldValue, - mixed $value, - ?CustomField $targetField - ): string { - if (! $targetField instanceof CustomField || ! $targetField->isChoiceField()) { - return $this->buildStandardNotEqualsExpression($fieldValue, $value); - } - - return $this->buildOptionExpression($fieldValue, $value, $targetField, 'not_equals'); - } - - /** - * Build standard equals expression for non-optionable fields. - * - * Two strings are compared case-insensitively to mirror VisibilityOperator::evaluateEquals(), - * which folds both sides through strtolower(). Without this the server shows a field for - * "active" vs "Active" while the client hides it. - */ - private function buildStandardEqualsExpression( - string $fieldValue, - mixed $value - ): string { - $jsValue = $this->formatJsValue($value); - - if (is_array($value)) { - return "(() => { - const fieldVal = {$fieldValue}; - const compareVal = {$jsValue}; - if (!Array.isArray(fieldVal) || !Array.isArray(compareVal)) return false; - const norm = a => JSON.stringify(a.map(v => String(v)).sort()); - return norm(fieldVal) === norm(compareVal); - })()"; - } - - return "(() => { - const fieldVal = {$fieldValue}; - const compareVal = {$jsValue}; - const isBlank = v => v === null || v === undefined; - const isNumericLike = v => typeof v !== 'boolean' && String(v).trim() !== '' && !isNaN(Number(v)); - - if (isBlank(fieldVal) && isBlank(compareVal)) { - return true; - } - - if (isBlank(fieldVal) || isBlank(compareVal)) { - return false; - } - - if (Array.isArray(fieldVal)) { - return fieldVal.map(v => String(v)).includes(String(compareVal)); - } - - if (typeof fieldVal === 'boolean' || typeof compareVal === 'boolean') { - return String(fieldVal).toLowerCase() === String(compareVal).toLowerCase(); - } - - if (typeof fieldVal === 'string' && typeof compareVal === 'string') { - return fieldVal.toLowerCase() === compareVal.toLowerCase(); - } - - if (isNumericLike(fieldVal) && isNumericLike(compareVal)) { - return Number(fieldVal) === Number(compareVal); - } - - return String(fieldVal) === String(compareVal); - })()"; - } - - /** - * Build standard not equals expression. - */ - private function buildStandardNotEqualsExpression( - string $fieldValue, - mixed $value - ): string { - $equalsExpression = $this->buildStandardEqualsExpression( - $fieldValue, - $value - ); - - return sprintf('!(%s)', $equalsExpression); - } - - /** - * Build option expression for optionable fields. - */ - private function buildOptionExpression( - string $fieldValue, - mixed $value, - CustomField $targetField, - string $operator - ): string { - $resolvedValue = $this->resolveOptionValue($value, $targetField); - $jsValue = $this->formatJsValue($resolvedValue); - - $typeData = $targetField->typeData; - $condition = ($typeData && $typeData->dataType->isMultiChoiceField()) - ? $this->buildMultiValueOptionCondition( - $fieldValue, - $resolvedValue, - $jsValue - ) - : $this->buildSingleValueOptionCondition($fieldValue, $jsValue); - - return Str::is('not_equals', $operator) - ? sprintf('!(%s)', $condition) - : $condition; - } - - /** - * Build multi-value option condition as a single-line expression (no block-body arrow, no double - * quotes) so it embeds safely inside Filament's `x-bind:class="{ 'fi-hidden': !(…) }"` attribute. - * Both sides are stringified before comparison because option ids arrive from Livewire state as - * strings while the resolved condition ids are integers — strict includes() would otherwise miss. - */ - private function buildMultiValueOptionCondition( - string $fieldValue, - mixed $resolvedValue, - string $jsValue - ): string { - $selected = sprintf('(Array.isArray(%s) ? %s : []).map(v => String(v))', $fieldValue, $fieldValue); - - return is_array($resolvedValue) - ? sprintf('(%s.map(v => String(v)).some(id => %s.includes(id)))', $jsValue, $selected) - : sprintf('(%s.includes(String(%s)))', $selected, $jsValue); - } - - /** - * Build single value option condition. - */ - private function buildSingleValueOptionCondition( - string $fieldValue, - string $jsValue - ): string { - $fieldEmpty = sprintf("(%s === null || %s === undefined || %s === '')", $fieldValue, $fieldValue, $fieldValue); - $conditionEmpty = sprintf("(%s === null || %s === undefined || %s === '')", $jsValue, $jsValue, $jsValue); - - // Single-line, string-compared (String() subsumes the number/boolean cases) so it stays safe - // inside Filament's double-quoted x-bind:class attribute. - return sprintf('(%s ? %s : String(%s) === String(%s))', $fieldEmpty, $conditionEmpty, $fieldValue, $jsValue); - } - - /** - * Resolve option value using the same logic as backend. - */ - private function resolveOptionValue( - mixed $value, - CustomField $targetField - ): mixed { - return match (true) { - blank($value) => $value, - is_array($value) => $this->resolveArrayOptionValue( - $value, - $targetField - ), - default => $this->convertOptionValue($value, $targetField), - }; - } - - /** - * Resolve array option value. - * - * @param array $value - */ - private function resolveArrayOptionValue( - array $value, - CustomField $targetField - ): mixed { - return $targetField->isMultiChoiceField() - ? collect($value) - ->map( - fn (mixed $v): mixed => $this->convertOptionValue($v, $targetField) - ) - ->all() - : $this->convertOptionValue(head($value), $targetField); - } - - /** - * Convert option value to proper format. - */ - private function convertOptionValue( - mixed $value, - CustomField $targetField - ): mixed { - if (blank($value)) { - return $value; - } - - if (is_numeric($value)) { - // Handle float values - if (is_float($value)) { - return $value; - } - - // Handle string values that contain decimal points - if (str_contains((string) $value, '.')) { - return (float) $value; - } - - // Handle integer values - return (int) $value; - } - - return rescue(function () use ($value, $targetField) { - if (is_string($value) && $targetField->options->isNotEmpty()) { - return $targetField->options->first( - fn (mixed $opt): bool => Str::lower(trim((string) $opt->name)) === - Str::lower(trim($value)) - )->id ?? $value; - } - - return $value; - }, $value); - } - - /** - * Build contains expression. - * - * For option-backed choice fields "contains" means exact option membership: the selected - * option ids include (any of) the condition's option ids. This matches the server, which - * evaluates the same condition as membership over normalized option names. Substring matching - * is kept only for free-text sources (text fields and option-less multi-value fields such as - * email/tags), where the client and server both compare raw values. - */ - private function buildContainsExpression( - string $fieldValue, - mixed $value, - ?CustomField $targetField - ): string { - if ($targetField instanceof CustomField && $targetField->isChoiceField() && $targetField->options->isNotEmpty()) { - return $this->buildOptionExpression($fieldValue, $value, $targetField, 'equals'); - } - - $resolvedValue = $targetField instanceof CustomField - ? $this->resolveOptionValue($value, $targetField) - : $value; - $jsValue = $this->formatJsValue($resolvedValue); - - return sprintf('(Array.isArray(%s) ', $fieldValue). - sprintf('? %s.some(item => String(item).toLowerCase().includes(String(%s).toLowerCase())) ', $fieldValue, $jsValue). - sprintf(": String(%s || '').toLowerCase().includes(String(%s).toLowerCase()))", $fieldValue, $jsValue); - } - - /** - * Build numeric comparison expression. - */ - private function buildNumericComparison( - string $fieldValue, - mixed $value, - string $operator - ): string { - return "(() => { - const fieldVal = parseFloat({$fieldValue}); - const compareVal = parseFloat({$this->formatJsValue($value)}); - return !isNaN(fieldVal) && !isNaN(compareVal) && fieldVal {$operator} compareVal; - })()"; - } - - /** - * Build empty expression. - */ - private function buildEmptyExpression( - string $fieldValue, - bool $isEmpty - ): string { - $condition = "(() => { - const val = {$fieldValue}; - return val === null || val === undefined || val === '' || (Array.isArray(val) && val.length === 0); - })()"; - - return $isEmpty ? $condition : sprintf('!(%s)', $condition); - } - - /** - * Format JavaScript value using the same logic as FieldConfigurator. - */ - private function formatJsValue(mixed $value): string - { - return match (true) { - $value === null => 'null', - is_bool($value) => $value ? 'true' : 'false', - $value === 'true' => 'true', - $value === 'false' => 'false', - is_string($value) => $this->toJsString($value), - is_int($value) => (string) $value, - is_float($value) => number_format($value, 10, '.', ''), - is_array($value) => collect($value) - ->map(fn (mixed $item): string => $this->formatJsValue($item)) - ->pipe( - fn (Collection $collection): string => '['. - $collection->implode(', '). - ']' - ), - default => $this->toJsString((string) $value), - }; - } - - /** - * Emit a single-quoted JS string literal. Single quotes (never double) keep the expression safe - * inside Filament's double-quoted `x-bind:class="…"` attribute, and control characters are - * stripped so the generated visibleJs never spans multiple lines or breaks Alpine parsing. - */ - private function toJsString(string $value): string - { - $escaped = str_replace( - ['\\', "'", "\r", "\n", "\t"], - ['\\\\', "\\'", '', ' ', ' '], - $value, - ); - - return sprintf("'%s'", $escaped); - } - /** * Export visibility logic to JavaScript format for complex integrations. * diff --git a/src/Services/Visibility/JsExpressionGenerator.php b/src/Services/Visibility/JsExpressionGenerator.php new file mode 100644 index 00000000..aa6d95e5 --- /dev/null +++ b/src/Services/Visibility/JsExpressionGenerator.php @@ -0,0 +1,382 @@ +coreLogic->isOperatorCompatible($operator, $targetField) + ) { + return null; + } + + return match ($operator) { + VisibilityOperator::EQUALS => $this->buildEqualsExpression( + $fieldValue, + $value, + $targetField + ), + VisibilityOperator::NOT_EQUALS => $this->buildNotEqualsExpression( + $fieldValue, + $value, + $targetField + ), + VisibilityOperator::CONTAINS => $this->buildContainsExpression( + $fieldValue, + $value, + $targetField + ), + VisibilityOperator::NOT_CONTAINS => transform( + $this->buildContainsExpression( + $fieldValue, + $value, + $targetField + ), + fn (?string $expr): string => sprintf('!(%s)', $expr) + ), + VisibilityOperator::GREATER_THAN => $this->buildNumericComparison( + $fieldValue, + $value, + '>' + ), + VisibilityOperator::LESS_THAN => $this->buildNumericComparison( + $fieldValue, + $value, + '<' + ), + VisibilityOperator::IS_EMPTY => $this->buildEmptyExpression( + $fieldValue, + true + ), + VisibilityOperator::IS_NOT_EMPTY => $this->buildEmptyExpression( + $fieldValue, + false + ), + // IS_IN / IS_NOT_IN are relation-only operators evaluated server-side. + // The set-level guard already returns null for relation conditions; this + // arm keeps the match exhaustive and emits no JS if one ever leaks through. + VisibilityOperator::IS_IN, VisibilityOperator::IS_NOT_IN => null, + }; + } + + /** + * Build equals expression with optionable field support. + */ + private function buildEqualsExpression( + string $fieldValue, + mixed $value, + ?CustomField $targetField + ): string { + if (! $targetField instanceof CustomField || ! $targetField->isChoiceField()) { + return $this->buildStandardEqualsExpression($fieldValue, $value); + } + + return $this->buildOptionExpression($fieldValue, $value, $targetField, 'equals'); + } + + /** + * Build not equals expression. + */ + private function buildNotEqualsExpression( + string $fieldValue, + mixed $value, + ?CustomField $targetField + ): string { + if (! $targetField instanceof CustomField || ! $targetField->isChoiceField()) { + return $this->buildStandardNotEqualsExpression($fieldValue, $value); + } + + return $this->buildOptionExpression($fieldValue, $value, $targetField, 'not_equals'); + } + + /** + * Build standard equals expression for non-optionable fields. + * + * Two strings are compared case-insensitively to mirror VisibilityOperator::evaluateEquals(), + * which folds both sides through strtolower(). Without this the server shows a field for + * "active" vs "Active" while the client hides it. + */ + private function buildStandardEqualsExpression( + string $fieldValue, + mixed $value + ): string { + $jsValue = $this->jsValues->format($value); + + if (is_array($value)) { + return "(() => { + const fieldVal = {$fieldValue}; + const compareVal = {$jsValue}; + if (!Array.isArray(fieldVal) || !Array.isArray(compareVal)) return false; + const norm = a => JSON.stringify(a.map(v => String(v)).sort()); + return norm(fieldVal) === norm(compareVal); + })()"; + } + + return "(() => { + const fieldVal = {$fieldValue}; + const compareVal = {$jsValue}; + const isBlank = v => v === null || v === undefined; + const isNumericLike = v => typeof v !== 'boolean' && String(v).trim() !== '' && !isNaN(Number(v)); + + if (isBlank(fieldVal) && isBlank(compareVal)) { + return true; + } + + if (isBlank(fieldVal) || isBlank(compareVal)) { + return false; + } + + if (Array.isArray(fieldVal)) { + return fieldVal.map(v => String(v)).includes(String(compareVal)); + } + + if (typeof fieldVal === 'boolean' || typeof compareVal === 'boolean') { + return String(fieldVal).toLowerCase() === String(compareVal).toLowerCase(); + } + + if (typeof fieldVal === 'string' && typeof compareVal === 'string') { + return fieldVal.toLowerCase() === compareVal.toLowerCase(); + } + + if (isNumericLike(fieldVal) && isNumericLike(compareVal)) { + return Number(fieldVal) === Number(compareVal); + } + + return String(fieldVal) === String(compareVal); + })()"; + } + + /** + * Build standard not equals expression. + */ + private function buildStandardNotEqualsExpression( + string $fieldValue, + mixed $value + ): string { + $equalsExpression = $this->buildStandardEqualsExpression( + $fieldValue, + $value + ); + + return sprintf('!(%s)', $equalsExpression); + } + + /** + * Build option expression for optionable fields. + */ + private function buildOptionExpression( + string $fieldValue, + mixed $value, + CustomField $targetField, + string $operator + ): string { + $resolvedValue = $this->resolveOptionValue($value, $targetField); + $jsValue = $this->jsValues->format($resolvedValue); + + $typeData = $targetField->typeData; + $condition = ($typeData && $typeData->dataType->isMultiChoiceField()) + ? $this->buildMultiValueOptionCondition( + $fieldValue, + $resolvedValue, + $jsValue + ) + : $this->buildSingleValueOptionCondition($fieldValue, $jsValue); + + return Str::is('not_equals', $operator) + ? sprintf('!(%s)', $condition) + : $condition; + } + + /** + * Build multi-value option condition as a single-line expression (no block-body arrow, no double + * quotes) so it embeds safely inside Filament's `x-bind:class="{ 'fi-hidden': !(…) }"` attribute. + * Both sides are stringified before comparison because option ids arrive from Livewire state as + * strings while the resolved condition ids are integers — strict includes() would otherwise miss. + */ + private function buildMultiValueOptionCondition( + string $fieldValue, + mixed $resolvedValue, + string $jsValue + ): string { + $selected = sprintf('(Array.isArray(%s) ? %s : []).map(v => String(v))', $fieldValue, $fieldValue); + + return is_array($resolvedValue) + ? sprintf('(%s.map(v => String(v)).some(id => %s.includes(id)))', $jsValue, $selected) + : sprintf('(%s.includes(String(%s)))', $selected, $jsValue); + } + + /** + * Build single value option condition. + */ + private function buildSingleValueOptionCondition( + string $fieldValue, + string $jsValue + ): string { + $fieldEmpty = sprintf("(%s === null || %s === undefined || %s === '')", $fieldValue, $fieldValue, $fieldValue); + $conditionEmpty = sprintf("(%s === null || %s === undefined || %s === '')", $jsValue, $jsValue, $jsValue); + + // Single-line, string-compared (String() subsumes the number/boolean cases) so it stays safe + // inside Filament's double-quoted x-bind:class attribute. + return sprintf('(%s ? %s : String(%s) === String(%s))', $fieldEmpty, $conditionEmpty, $fieldValue, $jsValue); + } + + /** + * Resolve option value using the same logic as backend. + */ + private function resolveOptionValue( + mixed $value, + CustomField $targetField + ): mixed { + return match (true) { + blank($value) => $value, + is_array($value) => $this->resolveArrayOptionValue( + $value, + $targetField + ), + default => $this->convertOptionValue($value, $targetField), + }; + } + + /** + * Resolve array option value. + * + * @param array $value + */ + private function resolveArrayOptionValue( + array $value, + CustomField $targetField + ): mixed { + return $targetField->isMultiChoiceField() + ? collect($value) + ->map( + fn (mixed $v): mixed => $this->convertOptionValue($v, $targetField) + ) + ->all() + : $this->convertOptionValue(head($value), $targetField); + } + + /** + * Convert option value to proper format. + */ + private function convertOptionValue( + mixed $value, + CustomField $targetField + ): mixed { + if (blank($value)) { + return $value; + } + + if (is_numeric($value)) { + // Handle float values + if (is_float($value)) { + return $value; + } + + // Handle string values that contain decimal points + if (str_contains((string) $value, '.')) { + return (float) $value; + } + + // Handle integer values + return (int) $value; + } + + return rescue(function () use ($value, $targetField) { + if (is_string($value) && $targetField->options->isNotEmpty()) { + return $targetField->options->first( + fn (mixed $opt): bool => Str::lower(trim((string) $opt->name)) === + Str::lower(trim($value)) + )->id ?? $value; + } + + return $value; + }, $value); + } + + /** + * Build contains expression. + * + * For option-backed choice fields "contains" means exact option membership: the selected + * option ids include (any of) the condition's option ids. This matches the server, which + * evaluates the same condition as membership over normalized option names. Substring matching + * is kept only for free-text sources (text fields and option-less multi-value fields such as + * email/tags), where the client and server both compare raw values. + */ + private function buildContainsExpression( + string $fieldValue, + mixed $value, + ?CustomField $targetField + ): string { + if ($targetField instanceof CustomField && $targetField->isChoiceField() && $targetField->options->isNotEmpty()) { + return $this->buildOptionExpression($fieldValue, $value, $targetField, 'equals'); + } + + $resolvedValue = $targetField instanceof CustomField + ? $this->resolveOptionValue($value, $targetField) + : $value; + $jsValue = $this->jsValues->format($resolvedValue); + + return sprintf('(Array.isArray(%s) ', $fieldValue). + sprintf('? %s.some(item => String(item).toLowerCase().includes(String(%s).toLowerCase())) ', $fieldValue, $jsValue). + sprintf(": String(%s || '').toLowerCase().includes(String(%s).toLowerCase()))", $fieldValue, $jsValue); + } + + /** + * Build numeric comparison expression. + */ + private function buildNumericComparison( + string $fieldValue, + mixed $value, + string $operator + ): string { + return "(() => { + const fieldVal = parseFloat({$fieldValue}); + const compareVal = parseFloat({$this->jsValues->format($value)}); + return !isNaN(fieldVal) && !isNaN(compareVal) && fieldVal {$operator} compareVal; + })()"; + } + + /** + * Build empty expression. + */ + private function buildEmptyExpression( + string $fieldValue, + bool $isEmpty + ): string { + $condition = "(() => { + const val = {$fieldValue}; + return val === null || val === undefined || val === '' || (Array.isArray(val) && val.length === 0); + })()"; + + return $isEmpty ? $condition : sprintf('!(%s)', $condition); + } +} diff --git a/src/Services/Visibility/JsValueFormatter.php b/src/Services/Visibility/JsValueFormatter.php new file mode 100644 index 00000000..cb4d9798 --- /dev/null +++ b/src/Services/Visibility/JsValueFormatter.php @@ -0,0 +1,50 @@ + 'null', + is_bool($value) => $value ? 'true' : 'false', + $value === 'true' => 'true', + $value === 'false' => 'false', + is_string($value) => $this->toJsString($value), + is_int($value) => (string) $value, + is_float($value) => number_format($value, 10, '.', ''), + is_array($value) => collect($value) + ->map(fn (mixed $item): string => $this->format($item)) + ->pipe( + fn (Collection $collection): string => '['. + $collection->implode(', '). + ']' + ), + default => $this->toJsString((string) $value), + }; + } + + /** + * Emit a single-quoted JS string literal. Single quotes (never double) keep the expression safe + * inside Filament's double-quoted `x-bind:class="…"` attribute, and control characters are + * stripped so the generated visibleJs never spans multiple lines or breaks Alpine parsing. + */ + private function toJsString(string $value): string + { + $escaped = str_replace( + ['\\', "'", "\r", "\n", "\t"], + ['\\\\', "\\'", '', ' ', ' '], + $value, + ); + + return sprintf("'%s'", $escaped); + } +} diff --git a/src/Support/CodeGenerator.php b/src/Support/CodeGenerator.php index 88857a16..4c9cecc9 100644 --- a/src/Support/CodeGenerator.php +++ b/src/Support/CodeGenerator.php @@ -6,6 +6,7 @@ use Closure; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; use Relaticle\CustomFields\CustomFields; use Relaticle\CustomFields\Enums\CustomFieldsFeature; @@ -17,7 +18,7 @@ */ final class CodeGenerator { - /** @var (Closure(string, string, int|string|null): (Closure(Builder): Builder)|null)|null */ + /** @var (Closure(string, string, int|string|null): (Closure(Builder): Builder)|null)|null */ private static ?Closure $uniquenessScopeResolver = null; /** @@ -35,7 +36,7 @@ public static function generateFromName(string $name): string * the code is being generated within (null when there is none). It returns a query * scope closure, or null to leave the check global. * - * @param (Closure(string, string, int|string|null): (Closure(Builder): Builder)|null)|null $callback + * @param (Closure(string, string, int|string|null): (Closure(Builder): Builder)|null)|null $callback */ public static function resolveUniquenessScopeUsing(?Closure $callback): void { @@ -73,6 +74,23 @@ public static function generateUniqueSectionCode(string $name, string $entityTyp ); } + /** + * Generate a unique code for a relationship definition, whose codes are unique per tenant + * rather than per entity type. + */ + public static function generateUniqueRelationshipCode(string $baseCode): string + { + $code = $baseCode; + $counter = 1; + + while (CustomFields::newRelationshipModel()->newQuery()->where('code', $code)->exists()) { + $code = sprintf('%s_%d', $baseCode, $counter); + $counter++; + } + + return $code; + } + /** * Check if a code already exists and append a counter if needed. */ diff --git a/src/Support/DatabaseFieldConstraints.php b/src/Support/DatabaseFieldConstraints.php index 4c63027d..3af69152 100644 --- a/src/Support/DatabaseFieldConstraints.php +++ b/src/Support/DatabaseFieldConstraints.php @@ -190,6 +190,10 @@ public static function clearCache(): void /** * Merge a single rule with existing rules. + * + * @param array $rules + * @param array $dbConstraints + * @return array */ private static function mergeRule( array $rules, @@ -240,6 +244,10 @@ private static function mergeRule( /** * Apply the stricter constraint between user and database rules. + * + * @param array $rules + * @param array $dbConstraints + * @return array */ private static function applyStricterConstraint( array $rules, diff --git a/src/Support/KeyType.php b/src/Support/KeyType.php new file mode 100644 index 00000000..5b23ac40 --- /dev/null +++ b/src/Support/KeyType.php @@ -0,0 +1,70 @@ + $table->id(), + self::ULID => $table->ulid('id')->primary(), + self::UUID => $table->uuid('id')->primary(), + }; + } + + public static function foreign(Blueprint $table, string $column): ForeignIdColumnDefinition + { + return match (self::current()) { + self::BIGINT => $table->foreignId($column), + self::ULID => $table->foreignUlid($column), + self::UUID => $table->foreignUuid($column), + }; + } + + /** + * Morph ends address host records, so the bigint default defers to the host's own + * Schema::defaultMorphKeyType() exactly as the pre-4.0 migrations do. + */ + public static function morphs(Blueprint $table, string $name, bool $nullable = false): void + { + $method = match (self::current()) { + self::BIGINT => $nullable ? 'nullableMorphs' : 'morphs', + self::ULID => $nullable ? 'nullableUlidMorphs' : 'ulidMorphs', + self::UUID => $nullable ? 'nullableUuidMorphs' : 'uuidMorphs', + }; + + $table->{$method}($name); + } + + /** + * @return self::BIGINT|self::ULID|self::UUID + */ + private static function current(): string + { + $keyType = (string) config('custom-fields.database.key_type', self::BIGINT); + + if (! in_array($keyType, [self::BIGINT, self::ULID, self::UUID], true)) { + throw new InvalidArgumentException(sprintf('Unsupported custom-fields database key type [%s].', $keyType)); + } + + return $keyType; + } +} diff --git a/src/Support/OptionNameParser.php b/src/Support/OptionNameParser.php new file mode 100644 index 00000000..205b38cd --- /dev/null +++ b/src/Support/OptionNameParser.php @@ -0,0 +1,66 @@ + $existingNames + * @return array{names: list, duplicates: int, truncated: bool} + */ + public static function parse(?string $input, array $existingNames = []): array + { + $seen = []; + + foreach ($existingNames as $existingName) { + if (is_string($existingName) && trim($existingName) !== '') { + $seen[mb_strtolower(trim($existingName))] = true; + } + } + + $names = []; + $duplicates = 0; + $truncated = false; + + foreach (preg_split('/\R/', (string) $input) ?: [] as $line) { + $name = trim($line); + + if ($name === '') { + continue; + } + + $key = mb_strtolower($name); + + if (isset($seen[$key])) { + $duplicates++; + + continue; + } + + if (count($names) === self::MAX_NAMES) { + $truncated = true; + + break; + } + + $seen[$key] = true; + $names[] = $name; + } + + return ['names' => $names, 'duplicates' => $duplicates, 'truncated' => $truncated]; + } +} diff --git a/src/Support/RelationshipTables.php b/src/Support/RelationshipTables.php new file mode 100644 index 00000000..86bda1c8 --- /dev/null +++ b/src/Support/RelationshipTables.php @@ -0,0 +1,20 @@ + Schema::hasTable((string) config('custom-fields.database.table_names.custom_field_links'))); + } +} diff --git a/src/Support/SafeValueConverter.php b/src/Support/SafeValueConverter.php index 53378978..7501e992 100644 --- a/src/Support/SafeValueConverter.php +++ b/src/Support/SafeValueConverter.php @@ -14,7 +14,7 @@ * Handles safe conversion of values to database-compatible formats * to prevent issues like numeric overflow. */ -class SafeValueConverter +final class SafeValueConverter { /** * Maximum allowable integer for BIGINT in most SQL databases diff --git a/src/Support/ThroughRelationResolver.php b/src/Support/ThroughRelationResolver.php new file mode 100644 index 00000000..3ce63896 --- /dev/null +++ b/src/Support/ThroughRelationResolver.php @@ -0,0 +1,152 @@ + + * + * @throws UnsupportedThroughRelationException + */ + public function resolve(Model $model, string $relation): Relation + { + $instance = $this->relationInstance($model, $relation); + + if (! $instance instanceof Relation) { + throw UnsupportedThroughRelationException::missing($model::class, $relation); + } + + if ($instance instanceof MorphTo) { + throw UnsupportedThroughRelationException::polymorphicTarget($model::class, $relation); + } + + if (! $instance instanceof BelongsTo && ! $instance instanceof HasOne && ! $instance instanceof MorphOne) { + throw UnsupportedThroughRelationException::toMany($model::class, $relation, $instance::class); + } + + $related = $instance->getRelated(); + + if (! $related instanceof HasCustomFields) { + throw UnsupportedThroughRelationException::withoutCustomFields($model::class, $relation, $related::class); + } + + return $instance; + } + + /** + * The related record a field is read from, or null when the row has none. + * + * @return (Model&HasCustomFields)|null + * + * @throws UnsupportedThroughRelationException + */ + public function relatedRecord(Model $record, string $relation): ?Model + { + $this->resolve($record, $relation); + + $related = $record->getAttribute($relation); + + return $related instanceof Model && $related instanceof HasCustomFields ? $related : null; + } + + /** + * Apply a constraint written against the related model to a row query. + * + * @param Builder $query + * @param Closure(Builder): mixed $constraint + * @return Builder + * + * @throws UnsupportedThroughRelationException + */ + public function constrain(Builder $query, string $relation, Closure $constraint): Builder + { + $this->resolve($query->getModel(), $relation); + + return $query->whereHas($relation, $constraint); + } + + /** + * @param Builder $query + * @return Builder + * + * @throws UnsupportedThroughRelationException + */ + public function orderByFieldValue(Builder $query, string $relation, CustomField $customField, string $direction): Builder + { + $instance = $this->resolve($query->getModel(), $relation); + + $keys = $instance->getRelationExistenceQuery( + $instance->getRelated()->newQueryWithoutRelationships(), + $query, + ); + + // The order has to see the rows the cell sees: the related model's global scopes and + // whatever the relation body constrains, which is what whereHas() merges as well. + $keys->mergeConstraintsFrom($instance->getQuery()); + + $values = $customField->values(); + + $values + ->select($customField->getValueColumn()) + ->whereIn( + $values->getRelated()->qualifyColumn('entity_id'), + // A self relation aliases the inner table, and only the returned builder + // knows the alias, so the key column is chosen after the correlation. + $keys->select($keys->getModel()->getQualifiedKeyName()), + ) + ->limit(1); + + $value = $values->getQuery(); + $sql = sprintf('(%s)', $value->toSql()); + + // Rows with no related record, or none the relation admits, sort last in both + // directions. The leading term says so without a NULLS LAST clause, which the MySQL + // family does not have. + return $query->orderByRaw( + sprintf('%s is null asc, %s %s', $sql, $sql, $this->sortDirection($direction)), + [...$value->getBindings(), ...$value->getBindings()], + ); + } + + private function sortDirection(string $direction): string + { + return strtolower($direction) === 'desc' ? 'desc' : 'asc'; + } + + /** + * @return Relation|null + */ + private function relationInstance(Model $model, string $relation): ?Relation + { + if (! $model->isRelation($relation)) { + return null; + } + + // Built the way an existence query is built: without the parent-key constraint, so the + // relation body's own wheres are all that travels with it. + /** @var Relation|mixed $instance */ + $instance = Relation::noConstraints(fn (): mixed => $model->{$relation}()); + + return $instance instanceof Relation ? $instance : null; + } +} diff --git a/src/Support/Utils.php b/src/Support/Utils.php index 9ba47e7c..c283ce0a 100644 --- a/src/Support/Utils.php +++ b/src/Support/Utils.php @@ -4,9 +4,6 @@ namespace Relaticle\CustomFields\Support; -use ReflectionClass; -use ReflectionException; - final class Utils { public static function getResourceCluster(): ?string @@ -51,23 +48,4 @@ public static function getTextColor(string $backgroundColor): string // Return black for light colors, white for dark colors return $luminance > 0.5 ? '#000000' : '#ffffff'; } - - /** - * Invoke a protected or private method on an object using reflection. - * - * @param object $object The object instance - * @param string $method The method name to invoke - * @param array $parameters The parameters to pass to the method - * @return mixed The method's return value - * - * @throws ReflectionException - */ - public static function invokeMethodByReflection(object $object, string $method, array $parameters = []): mixed - { - $reflection = new ReflectionClass($object); - $method = $reflection->getMethod($method); - $method->setAccessible(true); - - return $method->invokeArgs($object, $parameters); - } } diff --git a/src/Support/ViewFlavor.php b/src/Support/ViewFlavor.php new file mode 100644 index 00000000..67298b95 --- /dev/null +++ b/src/Support/ViewFlavor.php @@ -0,0 +1,105 @@ +value] ?? $configured; + } + + // Every surface reads the same two keys, so validating once at boot turns a typo into a + // failure on the first request rather than on the first render of a forked surface. + public static function validate(): void + { + try { + self::configured(); + self::overrides(); + } catch (InvalidArgumentException $invalidArgumentException) { + // A cached bad flavor has to stay recoverable: throwing here would take + // config:clear down with the config it exists to clear. + if (! app()->runningInConsole()) { + throw $invalidArgumentException; + } + + report($invalidArgumentException); + } + } + + // Null is the native flavor: the caller renders the view it shipped with, so a flavor + // decides what a surface looks like and never what it does. + public static function view(UiSurface $surface): ?string + { + return match (self::flavor($surface)) { + UiFlavor::Polished => 'custom-fields::flavors.polished.'.$surface->value, + UiFlavor::Native => null, + }; + } + + private static function configured(): UiFlavor + { + return self::parse(config('custom-fields.ui.flavor', UiFlavor::Polished->value), 'custom-fields.ui.flavor'); + } + + /** + * @return array + */ + private static function overrides(): array + { + $configured = config('custom-fields.ui.flavor_overrides', []); + + if (! is_array($configured)) { + throw new InvalidArgumentException('The custom-fields.ui.flavor_overrides config must be an array of surface keys to flavors.'); + } + + $overrides = []; + + foreach ($configured as $key => $flavor) { + $surface = UiSurface::tryFrom(self::stringify($key)); + + if (! $surface instanceof UiSurface) { + throw new InvalidArgumentException(sprintf( + 'Unknown custom-fields UI surface [%s] in custom-fields.ui.flavor_overrides. Forked surfaces are: %s.', + self::stringify($key), + implode(', ', array_column(UiSurface::cases(), 'value')), + )); + } + + $overrides[$surface->value] = self::parse($flavor, 'custom-fields.ui.flavor_overrides.'.$surface->value); + } + + return $overrides; + } + + private static function parse(mixed $flavor, string $configKey): UiFlavor + { + $parsed = UiFlavor::tryFrom(self::stringify($flavor)); + + if (! $parsed instanceof UiFlavor) { + throw new InvalidArgumentException(sprintf( + 'Unknown custom-fields UI flavor [%s] in %s. Available flavors are: %s.', + self::stringify($flavor), + $configKey, + implode(', ', array_column(UiFlavor::cases(), 'value')), + )); + } + + return $parsed; + } + + private static function stringify(mixed $value): string + { + return is_scalar($value) ? (string) $value : get_debug_type($value); + } +} diff --git a/src/Validation/Capabilities/AbstractDateCapability.php b/src/Validation/Capabilities/AbstractDateCapability.php index caf60416..e6b6f746 100644 --- a/src/Validation/Capabilities/AbstractDateCapability.php +++ b/src/Validation/Capabilities/AbstractDateCapability.php @@ -10,12 +10,12 @@ use Filament\Schemas\Components\Component; use Filament\Schemas\Components\Utilities\Get; use Illuminate\Database\Eloquent\Model; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; use Relaticle\CustomFields\Data\DateConstraintValue; use Relaticle\CustomFields\Filament\Management\Forms\Components\DateConstraintField; use Relaticle\CustomFields\Validation\Rules\DateConstraintRule; -abstract readonly class AbstractDateCapability implements ValidationCapability +abstract readonly class AbstractDateCapability implements ValidationCapabilityInterface { abstract protected function context(): string; diff --git a/src/Validation/Capabilities/AcceptedFileTypesCapability.php b/src/Validation/Capabilities/AcceptedFileTypesCapability.php index 21235544..b6308a67 100644 --- a/src/Validation/Capabilities/AcceptedFileTypesCapability.php +++ b/src/Validation/Capabilities/AcceptedFileTypesCapability.php @@ -7,9 +7,9 @@ use Filament\Forms\Components\Field; use Filament\Forms\Components\TagsInput; use Filament\Schemas\Components\Component; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; -final readonly class AcceptedFileTypesCapability implements ValidationCapability +final readonly class AcceptedFileTypesCapability implements ValidationCapabilityInterface { public function key(): string { diff --git a/src/Validation/Capabilities/DecimalPlacesCapability.php b/src/Validation/Capabilities/DecimalPlacesCapability.php index 8307672f..25bd8388 100644 --- a/src/Validation/Capabilities/DecimalPlacesCapability.php +++ b/src/Validation/Capabilities/DecimalPlacesCapability.php @@ -7,9 +7,9 @@ use Filament\Forms\Components\Field; use Filament\Forms\Components\TextInput; use Filament\Schemas\Components\Component; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; -final readonly class DecimalPlacesCapability implements ValidationCapability +final readonly class DecimalPlacesCapability implements ValidationCapabilityInterface { public function key(): string { diff --git a/src/Validation/Capabilities/MaxFileSizeCapability.php b/src/Validation/Capabilities/MaxFileSizeCapability.php index 5a81f9c4..3b45123a 100644 --- a/src/Validation/Capabilities/MaxFileSizeCapability.php +++ b/src/Validation/Capabilities/MaxFileSizeCapability.php @@ -7,9 +7,9 @@ use Filament\Forms\Components\Field; use Filament\Forms\Components\TextInput; use Filament\Schemas\Components\Component; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; -final readonly class MaxFileSizeCapability implements ValidationCapability +final readonly class MaxFileSizeCapability implements ValidationCapabilityInterface { public function key(): string { diff --git a/src/Validation/Capabilities/MaxLengthCapability.php b/src/Validation/Capabilities/MaxLengthCapability.php index 754be350..02bd6e87 100644 --- a/src/Validation/Capabilities/MaxLengthCapability.php +++ b/src/Validation/Capabilities/MaxLengthCapability.php @@ -7,9 +7,9 @@ use Filament\Forms\Components\Field; use Filament\Forms\Components\TextInput; use Filament\Schemas\Components\Component; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; -final readonly class MaxLengthCapability implements ValidationCapability +final readonly class MaxLengthCapability implements ValidationCapabilityInterface { public function key(): string { diff --git a/src/Validation/Capabilities/MaxSelectionsCapability.php b/src/Validation/Capabilities/MaxSelectionsCapability.php index b5fae4a7..4feb028a 100644 --- a/src/Validation/Capabilities/MaxSelectionsCapability.php +++ b/src/Validation/Capabilities/MaxSelectionsCapability.php @@ -7,9 +7,9 @@ use Filament\Forms\Components\Field; use Filament\Forms\Components\TextInput; use Filament\Schemas\Components\Component; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; -final readonly class MaxSelectionsCapability implements ValidationCapability +final readonly class MaxSelectionsCapability implements ValidationCapabilityInterface { public function key(): string { diff --git a/src/Validation/Capabilities/MaxValueCapability.php b/src/Validation/Capabilities/MaxValueCapability.php index e190f7f4..577e55a9 100644 --- a/src/Validation/Capabilities/MaxValueCapability.php +++ b/src/Validation/Capabilities/MaxValueCapability.php @@ -7,9 +7,9 @@ use Filament\Forms\Components\Field; use Filament\Forms\Components\TextInput; use Filament\Schemas\Components\Component; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; -final readonly class MaxValueCapability implements ValidationCapability +final readonly class MaxValueCapability implements ValidationCapabilityInterface { public function key(): string { diff --git a/src/Validation/Capabilities/MinLengthCapability.php b/src/Validation/Capabilities/MinLengthCapability.php index 006e7d1e..cb7da2a7 100644 --- a/src/Validation/Capabilities/MinLengthCapability.php +++ b/src/Validation/Capabilities/MinLengthCapability.php @@ -7,9 +7,9 @@ use Filament\Forms\Components\Field; use Filament\Forms\Components\TextInput; use Filament\Schemas\Components\Component; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; -final readonly class MinLengthCapability implements ValidationCapability +final readonly class MinLengthCapability implements ValidationCapabilityInterface { public function key(): string { diff --git a/src/Validation/Capabilities/MinSelectionsCapability.php b/src/Validation/Capabilities/MinSelectionsCapability.php index 03d09a2d..816289ba 100644 --- a/src/Validation/Capabilities/MinSelectionsCapability.php +++ b/src/Validation/Capabilities/MinSelectionsCapability.php @@ -7,9 +7,9 @@ use Filament\Forms\Components\Field; use Filament\Forms\Components\TextInput; use Filament\Schemas\Components\Component; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; -final readonly class MinSelectionsCapability implements ValidationCapability +final readonly class MinSelectionsCapability implements ValidationCapabilityInterface { public function key(): string { diff --git a/src/Validation/Capabilities/MinValueCapability.php b/src/Validation/Capabilities/MinValueCapability.php index 3e91998f..b7cbc2f2 100644 --- a/src/Validation/Capabilities/MinValueCapability.php +++ b/src/Validation/Capabilities/MinValueCapability.php @@ -7,9 +7,9 @@ use Filament\Forms\Components\Field; use Filament\Forms\Components\TextInput; use Filament\Schemas\Components\Component; -use Relaticle\CustomFields\Contracts\ValidationCapability; +use Relaticle\CustomFields\Contracts\ValidationCapabilityInterface; -final readonly class MinValueCapability implements ValidationCapability +final readonly class MinValueCapability implements ValidationCapabilityInterface { public function key(): string { diff --git a/stubs/custom-fields-migration.stub b/stubs/custom-fields-migration.stub index 501a59de..26af6e44 100644 --- a/stubs/custom-fields-migration.stub +++ b/stubs/custom-fields-migration.stub @@ -1,5 +1,7 @@ getExtension() !== 'php') { - continue; - } - - $relativePath = str_replace($srcPath.'/', '', $file->getPathname()); - - if (in_array($relativePath, $allowedFiles, true)) { - continue; - } - - $lines = explode("\n", file_get_contents($file->getPathname())); - - foreach ($lines as $lineNum => $line) { - if (str_contains($line, 'use ')) { - continue; - } - - if (str_contains($line, '//')) { - continue; - } - - if (preg_match($pattern, $line)) { - $violations[] = $relativePath.':'.($lineNum + 1).sprintf(' -> use %s instead', $facade); - } - } - } - - expect($violations)->toBeEmpty( - "Direct {$model} instantiation/querying found:\n".implode("\n", $violations), - ); -})->with([ - 'CustomField' => [ - 'model' => 'CustomField', - 'pattern' => '/(? 'CustomFields::newCustomFieldModel()', - 'allowedFiles' => ['CustomFields.php', 'Models/CustomField.php'], - ], - 'CustomFieldValue' => [ - 'model' => 'CustomFieldValue', - 'pattern' => '/CustomFieldValue::(query|where|find|create|first|all|get)\s*\(|new\s+CustomFieldValue[^a-zA-Z]/', - 'facade' => 'CustomFields::newValueModel()', - 'allowedFiles' => ['CustomFields.php', 'Models/CustomFieldValue.php'], - ], - 'CustomFieldOption' => [ - 'model' => 'CustomFieldOption', - 'pattern' => '/CustomFieldOption::(query|where|find|create|first|all|get)\s*\(|new\s+CustomFieldOption[^a-zA-Z]/', - 'facade' => 'CustomFields::newOptionModel()', - 'allowedFiles' => ['CustomFields.php', 'Models/CustomFieldOption.php'], - ], - 'CustomFieldSection' => [ - 'model' => 'CustomFieldSection', - 'pattern' => '/CustomFieldSection::(query|where|find|create|first|all|get)\s*\(|new\s+CustomFieldSection[^a-zA-Z]/', - 'facade' => 'CustomFields::newSectionModel()', - 'allowedFiles' => ['CustomFields.php', 'Models/CustomFieldSection.php'], - ], -]); - -arch('Models extend Eloquent Model') - ->expect([ - CustomField::class, - CustomFieldSection::class, - CustomFieldOption::class, - CustomFieldValue::class, - ]) - ->toExtend(Model::class); - -arch('Filament Resource extends base Resource') - ->expect(PostResource::class) - ->toExtend(Resource::class); - -arch('Filament Resource Pages extend base Page') - ->expect('Relaticle\CustomFields\Tests\Fixtures\Resources\Posts\Pages') - ->toExtend(Page::class); - -arch('No debugging functions are used') - ->expect(['dd', 'dump', 'ray', 'var_dump']) - ->not->toBeUsed(); - -arch('Enums are backed by strings or integers') - ->expect('Relaticle\CustomFields\Enums') - ->toBeEnums(); - -arch('Factories extend Laravel Factory') - ->expect('Relaticle\CustomFields\Database\Factories') - ->toExtend(Factory::class); - -arch('Custom field models implement HasCustomFields contract') - ->expect(Post::class) - ->toImplement(HasCustomFields::class) - ->toUse(UsesCustomFields::class); - -arch('Observers follow naming convention') - ->expect('Relaticle\CustomFields\Observers') - ->toHaveSuffix('Observer'); - -arch('Middleware follows naming convention') - ->expect('Relaticle\CustomFields\Http\Middleware') - ->toHaveSuffix('Middleware'); - -arch('Exceptions follow naming convention') - ->expect('Relaticle\CustomFields\Exceptions') - ->toHaveSuffix('Exception'); - -arch('Jobs follow proper structure') - ->expect('Relaticle\CustomFields\Jobs') - ->not->toHaveSuffix('Job'); - -arch('Data objects extend Spatie Data') - ->expect('Relaticle\CustomFields\Data') - ->toExtend(Data::class); - -// Enhanced service layer architecture tests -arch('Services follow naming convention') - ->expect('Relaticle\CustomFields\Services') - ->toHaveSuffix('Service'); - -arch('Service classes have single responsibility') - ->expect('Relaticle\CustomFields\Services') - ->toBeClasses() - ->and('Relaticle\CustomFields\Services') - ->not->toHaveMethodsMatching('/^(get|set).+And.+/'); // Avoid methods that do multiple things - -arch('Services use dependency injection properly') - ->expect('Relaticle\CustomFields\Services') - ->toBeClasses() - ->and('Relaticle\CustomFields\Services') - ->not->toUse(['new', 'static::']) // Avoid direct instantiation and static calls - ->ignoring([Cache::class, Log::class]); - -arch('No direct model usage in controllers') - ->expect('Relaticle\CustomFields\Http\Controllers') - ->not->toUse([ - CustomField::class, - CustomFieldSection::class, - CustomFieldValue::class, - CustomFieldOption::class, - ]); - -arch('Controllers delegate to services') - ->expect('Relaticle\CustomFields\Http\Controllers') - ->toUse('Relaticle\CustomFields\Services'); - -// Security and data protection constraints -arch('No password or secret data in logs') - ->expect(['password', 'secret', 'token', 'api_key']) - ->not->toBeUsedIn('Relaticle\CustomFields') - ->ignoring(['tests', 'Test', 'Factory']); - -arch('Encryption is used for sensitive data') - ->expect('Relaticle\CustomFields\Models') - ->toUse([Encrypter::class, 'encrypt', 'decrypt']) - ->when(fn ($class): bool => str_contains((string) $class, 'CustomField')); - -arch('Input validation is implemented') - ->expect('Relaticle\CustomFields\Http\Requests') - ->toHaveMethod('rules') - ->when(fn ($class): bool => class_exists($class)); - -arch('Filament forms use proper validation') - ->expect('Relaticle\CustomFields\Filament') - ->toUse(['Filament\\Forms\\Components']) - ->when(fn ($class): bool => str_contains((string) $class, 'Form')); - -// Performance constraints -arch('Database queries use proper indexing hints') - ->expect('Relaticle\CustomFields\Models') - ->not->toHaveMethodsMatching('/whereRaw|selectRaw|havingRaw/') - ->ignoring(['tests', 'Factory']); - -arch('No N+1 query patterns in services') - ->expect('Relaticle\CustomFields\Services') - ->not->toHaveMethodsMatching('/foreach.*->/') - ->ignoring(['tests']); - -arch('Caching is used for expensive operations') - ->expect('Relaticle\CustomFields\Services') - ->toUse([Cache::class, Repository::class]) - ->when(fn ($class): bool => str_contains((string) $class, 'Registry') || str_contains((string) $class, 'Helper')); - -// Type safety constraints -arch('All methods have return type declarations') - ->expect('Relaticle\CustomFields') - ->toHaveReturnTypeDeclarations() - ->ignoring(['tests', 'migrations', 'config']); - -arch('All parameters have type declarations') - ->expect('Relaticle\CustomFields') - ->toHaveParameterTypeDeclarations() - ->ignoring(['tests', 'migrations', 'config']); - -arch('Strict types are declared') - ->expect('Relaticle\CustomFields') - ->toUseStrictTypes() - ->ignoring(['config', 'lang']); - -// Testing constraints -arch('All test classes follow naming conventions') - ->expect('Relaticle\CustomFields\Tests') - ->toHaveSuffix('Test') - ->ignoring(['TestCase', 'Pest', 'helpers', 'Fixtures', 'Datasets']); - -arch('Tests use proper factories') - ->expect('Relaticle\CustomFields\Tests') - ->toUse('Relaticle\CustomFields\Database\Factories') - ->when(fn ($class): bool => str_contains((string) $class, 'Test')); - -arch('Feature tests use RefreshDatabase') - ->expect('Relaticle\CustomFields\Tests\Feature') - ->toUse(RefreshDatabase::class); - -// Package structure constraints -arch('Package follows proper namespace structure') - ->expect('Relaticle\CustomFields') - ->toHaveProperNamespaceStructure(); - -arch('No vendor dependencies in core models') - ->expect('Relaticle\CustomFields\Models') - ->not->toUse(['GuzzleHttp', 'Symfony\\Component\\HttpClient']) - ->ignoring(['Illuminate', 'Carbon', 'Spatie']); - -arch('Field type implementations are consistent') - ->expect('Relaticle\CustomFields\Services\FieldTypes') - ->toImplement(FieldTypeDefinitionInterface::class) - ->when(fn ($class): bool => class_exists($class)); - -// Integration constraints -arch('Filament form components implement proper interface') - ->expect('Relaticle\CustomFields\Filament\Integration\Components\Forms') - ->toImplement('Relaticle\CustomFields\Filament\Integration\Components\Forms\FieldComponentInterface') - ->ignoring(['AbstractFormComponent', 'FieldComponentInterface']); - -arch('Livewire components follow proper structure') - ->expect('Relaticle\CustomFields\Livewire') - ->toExtend(Component::class) - ->when(fn ($class): bool => class_exists($class)); - -// Data integrity constraints -arch('Models use proper casts for data integrity') - ->expect('Relaticle\CustomFields\Models') - ->toHaveProperty('casts') - ->when(fn ($class): bool => str_contains((string) $class, 'CustomField')); - -// Multi-tenancy constraints -arch('Tenant isolation is properly implemented') - ->expect('Relaticle\CustomFields\Models') - ->toUse([Filament::class, 'tenant']) - ->when(fn ($class): bool => str_contains((string) $class, 'CustomField')); - -arch('No global scopes bypass tenant isolation') - ->expect('Relaticle\CustomFields\Models') - ->not->toHaveMethodsMatching('/withoutGlobalScope|withoutGlobalScopes/') - ->ignoring(['tests']); - -// Error handling constraints -arch('Exceptions provide meaningful context') - ->expect('Relaticle\CustomFields\Exceptions') - ->toExtend('Exception') - ->toHaveMethod('__construct'); - -arch('No silent failures in critical operations') - ->expect('Relaticle\CustomFields\Services') - ->not->toHaveMethodsMatching('/try.*catch.*continue|try.*catch.*return null/'); - -// Documentation and code quality -arch('Public methods have docblocks') - ->expect('Relaticle\CustomFields') - ->toHaveDocumentedPublicMethods() - ->ignoring(['tests', 'migrations']); - -arch('Complex methods are properly documented') - ->expect('Relaticle\CustomFields') - ->toHaveDocumentedComplexMethods() - ->ignoring(['tests', 'migrations']); - -test('every HasLabel enum in Relaticle\\CustomFields\\Enums routes getLabel through __()', function (): void { - $dir = dirname(__DIR__).'/src/Enums'; - $files = glob($dir.'/*.php'); - - $violations = []; - - foreach ($files as $file) { - $class = 'Relaticle\\CustomFields\\Enums\\'.pathinfo($file, PATHINFO_FILENAME); - - if (! enum_exists($class)) { - continue; - } - - if (! is_subclass_of($class, HasLabel::class)) { - continue; - } - - $source = file_get_contents($file); - - if (! preg_match('/public function getLabel\(\)[^{]*\{(.*?)\n \}/s', $source, $m)) { - $violations[] = $class.': getLabel() not found'; - - continue; - } - - if (! str_contains($m[1], '__(')) { - $violations[] = $class.': getLabel() does not call __()'; - } - } - - expect($violations)->toBeEmpty(implode(PHP_EOL, $violations)); -}); - -test('every Action::make() in src/Livewire has a translated ->label()', function (): void { - $dir = dirname(__DIR__).'/src/Livewire'; - $files = glob($dir.'/*.php'); - - $violations = []; - - foreach ($files as $file) { - $source = file_get_contents($file); - - // Capture each `Action::make(...)` call plus its chained method calls up to the terminating `;`. - if (! preg_match_all('/(Action|BulkAction|TestAction)::make\([^)]+\).*?(?=\s*;|\)\s*,)/s', $source, $matches)) { - continue; - } - - foreach ($matches[0] as $chain) { - if (! preg_match('/->label\(\s*__\(/', $chain)) { - $violations[] = basename($file).': Action::make() without ->label(__()): '.substr(preg_replace('/\s+/', ' ', $chain), 0, 120); - } - } - } - - expect($violations)->toBeEmpty(implode(PHP_EOL, $violations)); -}); diff --git a/tests/ArchitectureTest.php b/tests/ArchitectureTest.php new file mode 100644 index 00000000..a11305d8 --- /dev/null +++ b/tests/ArchitectureTest.php @@ -0,0 +1,454 @@ +getExtension() !== 'php') { + continue; + } + + $relativePath = str_replace($srcPath.'/', '', $file->getPathname()); + + if (in_array($relativePath, $allowedFiles, true)) { + continue; + } + + $lines = explode("\n", file_get_contents($file->getPathname())); + + foreach ($lines as $lineNum => $line) { + if (str_contains($line, 'use ')) { + continue; + } + + if (str_contains($line, '//')) { + continue; + } + + if (preg_match($pattern, $line)) { + $violations[] = $relativePath.':'.($lineNum + 1).sprintf(' -> use %s instead', $facade); + } + } + } + + expect($violations)->toBeEmpty( + "Direct {$model} instantiation/querying found:\n".implode("\n", $violations), + ); +})->with([ + 'CustomField' => [ + 'model' => 'CustomField', + 'pattern' => '/(? 'CustomFields::newCustomFieldModel()', + 'allowedFiles' => ['CustomFields.php', 'Models/CustomField.php'], + ], + 'CustomFieldValue' => [ + 'model' => 'CustomFieldValue', + 'pattern' => '/CustomFieldValue::(query|where|find|create|first|all|get)\s*\(|new\s+CustomFieldValue[^a-zA-Z]/', + 'facade' => 'CustomFields::newValueModel()', + 'allowedFiles' => ['CustomFields.php', 'Models/CustomFieldValue.php'], + ], + 'CustomFieldOption' => [ + 'model' => 'CustomFieldOption', + 'pattern' => '/CustomFieldOption::(query|where|find|create|first|all|get)\s*\(|new\s+CustomFieldOption[^a-zA-Z]/', + 'facade' => 'CustomFields::newOptionModel()', + 'allowedFiles' => ['CustomFields.php', 'Models/CustomFieldOption.php'], + ], + 'CustomFieldSection' => [ + 'model' => 'CustomFieldSection', + 'pattern' => '/CustomFieldSection::(query|where|find|create|first|all|get)\s*\(|new\s+CustomFieldSection[^a-zA-Z]/', + 'facade' => 'CustomFields::newSectionModel()', + 'allowedFiles' => ['CustomFields.php', 'Models/CustomFieldSection.php'], + ], + 'CustomFieldRelationship' => [ + 'model' => 'CustomFieldRelationship', + 'pattern' => '/CustomFieldRelationship::(query|where|find|create|first|all|get)\s*\(|new\s+CustomFieldRelationship[^a-zA-Z]/', + 'facade' => 'CustomFields::newRelationshipModel()', + 'allowedFiles' => ['CustomFields.php', 'Models/CustomFieldRelationship.php'], + ], + 'CustomFieldLink' => [ + 'model' => 'CustomFieldLink', + 'pattern' => '/CustomFieldLink::(query|where|find|create|first|all|get)\s*\(|new\s+CustomFieldLink[^a-zA-Z]/', + 'facade' => 'CustomFields::newLinkModel()', + 'allowedFiles' => ['CustomFields.php', 'Models/CustomFieldLink.php'], + ], +]); + +arch('Models extend Eloquent Model') + ->expect([ + CustomField::class, + CustomFieldSection::class, + CustomFieldOption::class, + CustomFieldValue::class, + CustomFieldRelationship::class, + CustomFieldLink::class, + ]) + ->toExtend(Model::class); + +test('custom field models are scoped by the tenant scope', function (string $model): void { + $attributes = (new ReflectionClass($model))->getAttributes(ScopedBy::class); + + expect($attributes)->not->toBeEmpty($model.' carries no ScopedBy attribute.'); + + $scopes = array_merge(...array_map( + fn (ReflectionAttribute $attribute): array => (array) ($attribute->getArguments()[0] ?? []), + $attributes, + )); + + expect($scopes)->toContain(TenantScope::class); +})->with([ + CustomField::class, + CustomFieldSection::class, + CustomFieldOption::class, + CustomFieldValue::class, + CustomFieldRelationship::class, + CustomFieldLink::class, +]); + +arch('Filament Resource extends base Resource') + ->expect(PostResource::class) + ->toExtend(Resource::class); + +arch('Filament Resource Pages extend base Page') + ->expect('Relaticle\CustomFields\Tests\Fixtures\Resources\Posts\Pages') + ->toExtend(Page::class); + +arch('No debugging functions are used') + ->expect(['dd', 'dump', 'ray', 'var_dump']) + ->not->toBeUsed(); + +arch('Enums are backed by strings or integers') + ->expect('Relaticle\CustomFields\Enums') + ->toBeEnums(); + +arch('Factories extend Laravel Factory') + ->expect('Relaticle\CustomFields\Database\Factories') + ->toExtend(Factory::class); + +arch('Custom field models implement HasCustomFields contract') + ->expect(Post::class) + ->toImplement(HasCustomFields::class) + ->toUse(UsesCustomFields::class); + +arch('Observers follow naming convention') + ->expect('Relaticle\CustomFields\Observers') + ->toHaveSuffix('Observer'); + +arch('Middleware follows naming convention') + ->expect('Relaticle\CustomFields\Http\Middleware') + ->toHaveSuffix('Middleware'); + +arch('Exceptions follow naming convention') + ->expect('Relaticle\CustomFields\Exceptions') + ->toHaveSuffix('Exception'); + +arch('Data objects extend Spatie Data') + ->expect('Relaticle\CustomFields\Data') + ->toExtend(Data::class); + +arch('Field type definitions implement the field type interface') + ->expect('Relaticle\CustomFields\FieldTypeSystem\Definitions') + ->toImplement(FieldTypeDefinitionInterface::class); + +arch('Filament form components implement the shared form component interface') + ->expect('Relaticle\CustomFields\Filament\Integration\Components\Forms') + ->toImplement(FormComponentInterface::class) + ->ignoring([ + 'Relaticle\CustomFields\Filament\Integration\Components\Forms\PhoneInput', + 'Relaticle\CustomFields\Filament\Integration\Components\Forms\MultiValueInput', + 'Relaticle\CustomFields\Filament\Integration\Components\Forms\RecordSelectInput', + 'Relaticle\CustomFields\Filament\Integration\Components\Forms\RelationshipPicker', + ]); + +arch('Livewire components extend the base Component class') + ->expect('Relaticle\CustomFields\Livewire') + ->toExtend(Component::class) + ->ignoring(['Relaticle\CustomFields\Livewire\Concerns']); + +arch('No vendor dependencies in core models') + ->expect('Relaticle\CustomFields\Models') + ->not->toUse(['GuzzleHttp', 'Symfony\\Component\\HttpClient']) + ->ignoring(['Illuminate', 'Carbon', 'Spatie']); + +arch('Strict types are declared') + ->expect('Relaticle\CustomFields') + ->toUseStrictTypes(); + +// The arch rule above only reaches classes, so it never sees the tests, the config file, +// the stubs, or a migration; those are exactly the files that keep losing the declaration. +test('every PHP file outside the source tree declares strict types', function (): void { + $root = dirname(__DIR__); + + $files = [ + ...glob($root.'/config/*.php'), + ...glob($root.'/stubs/*.stub'), + ]; + + foreach ([$root.'/tests', $root.'/database'] as $directory) { + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS), + ); + + foreach ($iterator as $file) { + // A Blade template ends in .php and can carry no declaration of its own. + if ($file->getExtension() === 'php' && ! str_ends_with($file->getBasename(), '.blade.php')) { + $files[] = $file->getPathname(); + } + } + } + + $violations = []; + + foreach ($files as $file) { + $tokens = array_values(array_filter( + token_get_all(file_get_contents($file)), + fn (array|string $token): bool => ! is_array($token) + || ! in_array($token[0], [T_OPEN_TAG, T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true), + )); + + $first = $tokens[0] ?? null; + + if (is_array($first) && $first[0] === T_DECLARE && str_contains($tokens[2][1] ?? '', 'strict_types')) { + continue; + } + + $violations[] = str_replace($root.'/', '', $file); + } + + expect($violations)->toBeEmpty( + "Files without declare(strict_types=1) as their first statement:\n".implode("\n", $violations), + ); +}); + +// Two kinds of entry are ignored: things that cannot carry the keyword (interfaces, traits, +// abstract bases, enums) and the seams documented in +// docs/content/2.essentials/8.extending.md. Opening a class means adding it there too. +arch('Classes are final outside the documented extension points') + ->expect('Relaticle\CustomFields') + ->toBeFinal() + ->ignoring([ + 'Relaticle\CustomFields\Concerns', + UpgradeStep::class, + 'Relaticle\CustomFields\Contracts', + CustomFieldsPlugin::class, + 'Relaticle\CustomFields\Enums', + BaseFieldType::class, + 'Relaticle\CustomFields\FieldTypeSystem\Concerns', + 'Relaticle\CustomFields\FieldTypeSystem\Definitions', + 'Relaticle\CustomFields\Filament\Integration\Base', + BaseBuilder::class, + 'Relaticle\CustomFields\Filament\Integration\Components\Forms\MultiValueInput', + 'Relaticle\CustomFields\Filament\Integration\Components\Forms\PhoneInput', + 'Relaticle\CustomFields\Filament\Integration\Components\Forms\RecordSelectInput', + DateTimeColumn::class, + IconColumn::class, + 'Relaticle\CustomFields\Filament\Integration\Concerns', + AbstractComponentFactory::class, + 'Relaticle\CustomFields\Filament\Integration\Factories\Concerns', + CustomFieldsMigration::class, + CustomFieldsManagementPage::class, + FormInterface::class, + SectionFormInterface::class, + 'Relaticle\CustomFields\Jobs\Concerns', + 'Relaticle\CustomFields\Livewire\Concerns', + 'Relaticle\CustomFields\Models\Concerns', + 'Relaticle\CustomFields\Models\Contracts', + CustomField::class, + CustomFieldRelationship::class, + CustomFieldLink::class, + ActivableScope::class, + CustomFieldQueryBuilder::class, + AbstractDateCapability::class, + ]); + +arch('All test classes follow naming conventions') + ->expect('Relaticle\CustomFields\Tests') + ->toHaveSuffix('Test') + ->ignoring([ + TestCase::class, + 'Relaticle\CustomFields\Tests\Fixtures', + 'Relaticle\CustomFields\Tests\Datasets', + 'Relaticle\CustomFields\Tests\Database\Factories', + ]); + +arch('Exceptions extend the base exception') + ->expect('Relaticle\CustomFields\Exceptions') + ->toExtend('Exception'); + +test('every HasLabel enum in Relaticle\\CustomFields\\Enums routes getLabel through __()', function (): void { + $dir = dirname(__DIR__).'/src/Enums'; + $files = glob($dir.'/*.php'); + + $violations = []; + + foreach ($files as $file) { + $class = 'Relaticle\\CustomFields\\Enums\\'.pathinfo($file, PATHINFO_FILENAME); + + if (! enum_exists($class)) { + continue; + } + + if (! is_subclass_of($class, HasLabel::class)) { + continue; + } + + $source = file_get_contents($file); + + if (! preg_match('/public function getLabel\(\)[^{]*\{(.*?)\n \}/s', $source, $m)) { + $violations[] = $class.': getLabel() not found'; + + continue; + } + + if (! str_contains($m[1], '__(')) { + $violations[] = $class.': getLabel() does not call __()'; + } + } + + expect($violations)->toBeEmpty(implode(PHP_EOL, $violations)); +}); + +test('every Action::make() in src/Livewire has a translated ->label()', function (): void { + $dir = dirname(__DIR__).'/src/Livewire'; + $files = glob($dir.'/*.php'); + + $violations = []; + + foreach ($files as $file) { + $source = file_get_contents($file); + + // Capture each `Action::make(...)` call plus its chained method calls up to the terminating `;`. + if (! preg_match_all('/(Action|BulkAction|TestAction)::make\([^)]+\).*?(?=\s*;|\)\s*,)/s', $source, $matches)) { + continue; + } + + foreach ($matches[0] as $chain) { + if (! preg_match('/->label\(\s*__\(/', $chain)) { + $violations[] = basename($file).': Action::make() without ->label(__()): '.substr(preg_replace('/\s+/', ' ', $chain), 0, 120); + } + } + } + + expect($violations)->toBeEmpty(implode(PHP_EOL, $violations)); +}); + +/** + * Two classes in one file compile only while the parent of the first is already loaded: + * autoloading it cold makes PHP resolve a return type declared before the class carrying + * it, and the process dies with "Could not check compatibility". + */ +test('every source file declares exactly one type, named after the file', function (): void { + $srcPath = dirname(__DIR__).'/src'; + $violations = []; + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($srcPath, FilesystemIterator::SKIP_DOTS), + ); + + foreach ($iterator as $file) { + if ($file->getExtension() !== 'php') { + continue; + } + + $declared = declaredTypeNames($file->getPathname()); + $relativePath = str_replace($srcPath.'/', '', $file->getPathname()); + + if (count($declared) !== 1) { + $violations[] = $relativePath.' declares '.($declared === [] ? 'no type' : implode(', ', $declared)); + + continue; + } + + if ($declared[0] !== $file->getBasename('.php')) { + $violations[] = $relativePath.' declares '.$declared[0]; + } + } + + expect($violations)->toBeEmpty(implode(PHP_EOL, $violations)); +}); + +/** + * @return array + */ +function declaredTypeNames(string $path): array +{ + $tokens = token_get_all((string) file_get_contents($path)); + $names = []; + + foreach ($tokens as $index => $token) { + if (! is_array($token)) { + continue; + } + + if (! in_array($token[0], [T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM], true)) { + continue; + } + + $twoBack = $tokens[$index - 2] ?? null; + $previous = $tokens[$index - 1] ?? null; + + // `new class` declares nothing importable, and `Foo::class` is not a declaration. + if (is_array($twoBack) && $twoBack[0] === T_NEW) { + continue; + } + + if (is_array($previous) && $previous[0] === T_DOUBLE_COLON) { + continue; + } + + $next = $index + 1; + + while (isset($tokens[$next]) && is_array($tokens[$next]) && in_array($tokens[$next][0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) { + $next++; + } + + if (isset($tokens[$next]) && is_array($tokens[$next]) && $tokens[$next][0] === T_STRING) { + $names[] = $tokens[$next][1]; + } + } + + return $names; +} diff --git a/tests/Feature/Admin/Pages/CustomFieldsFieldManagementTest.php b/tests/Feature/Admin/Pages/CustomFieldsFieldManagementTest.php index 64c316ff..5b43c6e9 100644 --- a/tests/Feature/Admin/Pages/CustomFieldsFieldManagementTest.php +++ b/tests/Feature/Admin/Pages/CustomFieldsFieldManagementTest.php @@ -4,10 +4,19 @@ use Relaticle\CustomFields\CustomFields; use Relaticle\CustomFields\Data\CustomFieldOptionSettingsData; +use Relaticle\CustomFields\Data\FieldSlotData; +use Relaticle\CustomFields\Data\RelationshipDefinitionData; +use Relaticle\CustomFields\Enums\RelationshipCardinality; +use Relaticle\CustomFields\FieldTypeSystem\Definitions\RelationshipFieldType; use Relaticle\CustomFields\Livewire\ManageCustomField; use Relaticle\CustomFields\Livewire\ManageCustomFieldSection; +use Relaticle\CustomFields\Livewire\ManageFieldsTable; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldLink; +use Relaticle\CustomFields\Models\CustomFieldRelationship; use Relaticle\CustomFields\Models\CustomFieldSection; +use Relaticle\CustomFields\Services\Relationships\CreateRelationshipDefinition; +use Relaticle\CustomFields\Tests\Fixtures\Models\Comment; use Relaticle\CustomFields\Tests\Fixtures\Models\Post; use Relaticle\CustomFields\Tests\Fixtures\Models\User; @@ -490,6 +499,41 @@ ])->assertActionHidden('duplicate'); }); + it('creates a tags input field without asking for an option nobody typed', function (): void { + livewire(ManageCustomFieldSection::class, [ + 'section' => $this->section, + 'entityType' => $this->userEntityType, + ]) + ->callAction('createField', [ + 'name' => 'Labels', + 'code' => 'labels', + 'type' => 'tags-input', + 'entity_type' => $this->userEntityType, + ]) + ->assertHasNoActionErrors(); + + $field = CustomField::query()->withoutGlobalScopes()->where('code', 'labels')->firstOrFail(); + + expect($field->type)->toBe('tags-input') + ->and($field->options)->toBeEmpty(); + }); + + it('refuses to create a select field with no options', function (): void { + livewire(ManageCustomFieldSection::class, [ + 'section' => $this->section, + 'entityType' => $this->userEntityType, + ]) + ->callAction('createField', [ + 'name' => 'Stage', + 'code' => 'stage', + 'type' => 'select', + 'entity_type' => $this->userEntityType, + ]) + ->assertHasActionErrors(['options' => 'required_unless']); + + expect(CustomField::query()->withoutGlobalScopes()->where('code', 'stage')->exists())->toBeFalse(); + }); + it('sets sort_order on options when creating a select field via storeField', function (): void { livewire(ManageCustomFieldSection::class, [ 'section' => $this->section, @@ -824,22 +868,7 @@ expect($field)->toHaveFieldType($fieldType); } }); - it('validates field type constraints and behaviors', function (): void { - // Test text field constraints - $textField = CustomField::factory() - ->ofType('text') - ->create([ - 'custom_field_section_id' => $this->section->getKey(), - 'entity_type' => $this->userEntityType, - ]); - - livewire(ManageCustomField::class, [ - 'field' => $textField, - ]) - ->assertSuccessful() - ->assertSee($textField->name); - - // Test select field with options constraint + it('loads the stored options into the form when editing a select field', function (): void { $selectField = CustomField::factory() ->ofType('select') ->withOptions([ @@ -851,19 +880,20 @@ 'entity_type' => $this->userEntityType, ]); - expect($selectField->options)->toHaveCount(2); - - livewire(ManageCustomField::class, [ + $page = livewire(ManageCustomField::class, [ 'field' => $selectField, ]) ->assertSuccessful() ->mountAction('edit', ['record' => $selectField->getKey()]) - ->callMountedAction() - ->assertSee([ - 'Option 1', - 'Option 2', - ]); - })->todo(); + ->assertActionMounted('edit') + ->assertSchemaComponentVisible('options'); + + $component = $page->instance(); + $schema = $component->{$component->getMountedActionSchemaName()}; + + expect(collect($schema->getRawState()['options'])->pluck('name')->all()) + ->toBe(['Option 1', 'Option 2']); + }); it('can handle field section management and organization', function (): void { // Create multiple sections @@ -985,3 +1015,276 @@ ->name->toBe('HMIS ID (Q/A testing added)'); }); }); + +describe('ManageFieldsTable - Field Management', function (): void { + it('creates a tags input field without asking for an option nobody typed', function (): void { + CustomFieldSection::factory()->forEntityType(Post::class)->create(); + + livewire(ManageFieldsTable::class, ['entityType' => Post::class]) + ->callAction('createField', [ + 'name' => 'Labels', + 'code' => 'labels', + 'type' => 'tags-input', + 'entity_type' => Post::class, + ]) + ->assertHasNoActionErrors(); + + $field = CustomField::query()->withoutGlobalScopes()->where('code', 'labels')->firstOrFail(); + + expect($field->type)->toBe('tags-input') + ->and($field->options)->toBeEmpty(); + }); + + it('refuses to create a select field with no options', function (): void { + CustomFieldSection::factory()->forEntityType(Post::class)->create(); + + livewire(ManageFieldsTable::class, ['entityType' => Post::class]) + ->callAction('createField', [ + 'name' => 'Stage', + 'code' => 'stage', + 'type' => 'select', + 'entity_type' => Post::class, + ]) + ->assertHasActionErrors(['options' => 'required_unless']); + + expect(CustomField::query()->withoutGlobalScopes()->where('code', 'stage')->exists())->toBeFalse(); + }); + + it('edits a select field without duplicating its stored options', function (): void { + $section = CustomFieldSection::factory()->forEntityType(Post::class)->create(); + + $field = CustomField::factory() + ->ofType('select') + ->withOptions(['Option 1', 'Option 2']) + ->create([ + 'custom_field_section_id' => $section->getKey(), + 'entity_type' => Post::class, + ]); + + livewire(ManageFieldsTable::class, ['entityType' => Post::class]) + ->mountAction('editField', ['fieldId' => $field->getKey()]) + ->assertActionDataSet(['name' => $field->name]) + ->set('mountedActions.0.data.name', 'Renamed Field') + ->callMountedAction() + ->assertHasNoActionErrors(); + + expect($field->refresh()->name)->toBe('Renamed Field') + ->and($field->options()->pluck('name')->all())->toBe(['Option 1', 'Option 2']); + }); +}); + +function pairedCommentAuthorship(CustomFieldSection $postSection, CustomFieldSection $commentSection): CustomFieldRelationship +{ + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'comment_authorship', + fromEntityType: Post::class, + toEntityType: Comment::class, + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Lead Comment', sectionId: $postSection->getKey(), type: RelationshipFieldType::KEY), + toField: new FieldSlotData(name: 'Leads For', sectionId: $commentSection->getKey(), type: RelationshipFieldType::KEY), + )); +} + +describe('Record field configuration', function (): void { + beforeEach(function (): void { + $this->postSection = CustomFieldSection::factory()->forEntityType(Post::class)->create(); + $this->commentSection = CustomFieldSection::factory()->forEntityType(Comment::class)->create(); + }); + + it('creates a one-way record field on a definition of its own', function (): void { + livewire(ManageCustomFieldSection::class, [ + 'section' => $this->postSection, + 'entityType' => Post::class, + ]) + ->callAction('createField', [ + 'name' => 'Related Comment', + 'code' => 'related_comment', + 'type' => 'record', + 'entity_type' => Post::class, + 'relationship' => [ + 'target_entity_type' => Comment::class, + 'cardinality' => RelationshipCardinality::ManyToOne->value, + ], + ]) + ->assertHasNoActionErrors(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect($definition->from_entity_type)->toBe(Post::class) + ->and($definition->to_entity_type)->toBe(Comment::class) + ->and($definition->cardinality)->toBe(RelationshipCardinality::ManyToOne) + ->and($definition->is_symmetric)->toBeFalse() + ->and($definition->fromField->code)->toBe('related_comment') + ->and($definition->to_field_id)->toBeNull() + ->and(CustomField::query()->count())->toBe(1); + }); + + it('creates the paired field on the target entity when it is named', function (): void { + livewire(ManageCustomFieldSection::class, [ + 'section' => $this->postSection, + 'entityType' => Post::class, + ]) + ->callAction('createField', [ + 'name' => 'Related Comment', + 'code' => 'related_comment', + 'type' => RelationshipFieldType::KEY, + 'entity_type' => Post::class, + 'relationship' => [ + 'target_entity_type' => Comment::class, + 'cardinality' => RelationshipCardinality::ManyToMany->value, + 'paired_field_name' => 'Related Post', + 'paired_section_id' => $this->commentSection->getKey(), + ], + ]) + ->assertHasNoActionErrors(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect(CustomField::query()->count())->toBe(2) + ->and($definition->fromField->code)->toBe('related_comment') + ->and($definition->toField->name)->toBe('Related Post') + ->and($definition->toField->entity_type)->toBe(Comment::class) + ->and($definition->toField->custom_field_section_id)->toBe($this->commentSection->getKey()); + }); + + it('loads the definition into the edit form and keeps the ends where they are', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_comment', + fromEntityType: Post::class, + toEntityType: Comment::class, + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Related Comment', sectionId: $this->postSection->getKey()), + )); + + livewire(ManageCustomField::class, ['field' => $definition->fromField]) + ->mountAction('edit') + ->assertActionDataSet([ + 'relationship.target_entity_type' => Comment::class, + 'relationship.cardinality' => RelationshipCardinality::ManyToMany->value, + 'relationship.is_symmetric' => false, + ]) + ->set('mountedActions.0.data.relationship.target_entity_type', Post::class) + ->callMountedAction() + ->assertHasNoActionErrors(); + + expect($definition->refresh()->to_entity_type)->toBe(Comment::class); + }); + + it('gives a duplicated record field a definition of its own', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_comment', + fromEntityType: Post::class, + toEntityType: Comment::class, + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Related Comment', sectionId: $this->postSection->getKey()), + )); + + livewire(ManageCustomField::class, ['field' => $definition->fromField]) + ->callAction('duplicate'); + + $copy = CustomField::query()->whereKeyNot($definition->from_field_id)->sole(); + + expect(CustomFieldRelationship::query()->count())->toBe(2) + ->and($copy->targetEntityType())->toBe(Comment::class) + ->and($copy->relationshipDefinition()->cardinality)->toBe(RelationshipCardinality::ManyToMany); + }); + + it('pairs onto an entity with no sections by creating a default one', function (): void { + $this->commentSection->delete(); + + livewire(ManageCustomFieldSection::class, [ + 'section' => $this->postSection, + 'entityType' => Post::class, + ]) + ->callAction('createField', [ + 'name' => 'Related Comment', + 'code' => 'related_comment', + 'type' => RelationshipFieldType::KEY, + 'entity_type' => Post::class, + 'relationship' => [ + 'target_entity_type' => Comment::class, + 'cardinality' => RelationshipCardinality::ManyToMany->value, + 'paired_field_name' => 'Related Post', + ], + ]) + ->assertHasNoActionErrors(); + + $paired = CustomFieldRelationship::query()->sole()->toField; + + expect($paired->name)->toBe('Related Post') + ->and($paired->section)->not->toBeNull() + ->and($paired->section->entity_type)->toBe(Comment::class) + ->and(CustomField::query()->whereKey($paired->getKey())->exists())->toBeTrue(); + }); + + it('never offers a symmetric toggle across an entity the host has not registered', function (): void { + livewire(ManageCustomFieldSection::class, [ + 'section' => $this->postSection, + 'entityType' => Post::class, + ]) + ->mountAction('createField') + ->set('mountedActions.0.data.type', RelationshipFieldType::KEY) + ->set('mountedActions.0.data.entity_type', 'ghost_entity') + ->set('mountedActions.0.data.relationship.target_entity_type', 'other_ghost_entity') + ->assertSchemaComponentHidden('relationship.is_symmetric'); + }); + + it('keeps a duplicated to-end field pointing the way it read', function (): void { + $definition = pairedCommentAuthorship($this->postSection, $this->commentSection); + $toField = $definition->toField; + + expect($toField->allowsMultipleRecords())->toBeTrue(); + + livewire(ManageCustomField::class, ['field' => $toField]) + ->callAction('duplicate'); + + $copy = CustomField::query() + ->where('entity_type', Comment::class) + ->whereKeyNot($toField->getKey()) + ->sole(); + + expect($copy->targetEntityType())->toBe(Post::class) + ->and($copy->allowsMultipleRecords())->toBeTrue() + ->and($copy->relationshipDefinition()->cardinality)->toBe(RelationshipCardinality::OneToMany); + }); + + it('shows and stores a to-end field the cardinality from its own side', function (): void { + $definition = pairedCommentAuthorship($this->postSection, $this->commentSection); + + livewire(ManageCustomField::class, ['field' => $definition->toField]) + ->mountAction('edit') + ->assertActionDataSet(['relationship.cardinality' => RelationshipCardinality::OneToMany->value]) + ->set('mountedActions.0.data.relationship.cardinality', RelationshipCardinality::ManyToOne->value) + ->set('mountedActions.0.data.relationship.keep_first', true) + ->callMountedAction() + ->assertHasNoActionErrors(); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::OneToMany) + ->and($definition->toField->allowsMultipleRecords())->toBeFalse(); + }); + + it('narrows the cardinality on confirmation, keeping the first linked record', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_comment', + fromEntityType: Post::class, + toEntityType: Comment::class, + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Related Comment', sectionId: $this->postSection->getKey()), + )); + + $field = $definition->fromField; + [$first, $second] = Comment::factory()->count(2)->create(); + $post = Post::factory()->create(['custom_fields' => [$field->code => [$first->getKey(), $second->getKey()]]]); + + livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->set('mountedActions.0.data.relationship.allow_multiple', false) + ->set('mountedActions.0.data.relationship.keep_first', true) + ->callMountedAction() + ->assertHasNoActionErrors(); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::ManyToOne) + ->and($post->fresh()->getCustomFieldValue($field->fresh()))->toBe([$first->getKey()]) + ->and(CustomFieldLink::query()->whereNotNull('active_until')->count())->toBe(1); + }); +}); diff --git a/tests/Feature/Admin/Pages/CustomFieldsPageRenderingTest.php b/tests/Feature/Admin/Pages/CustomFieldsPageRenderingTest.php index 0bacdd40..ae960551 100644 --- a/tests/Feature/Admin/Pages/CustomFieldsPageRenderingTest.php +++ b/tests/Feature/Admin/Pages/CustomFieldsPageRenderingTest.php @@ -2,6 +2,8 @@ declare(strict_types=1); +use Relaticle\CustomFields\Enums\CustomFieldsFeature; +use Relaticle\CustomFields\FeatureSystem\FeatureConfigurator; use Relaticle\CustomFields\Filament\Management\Pages\CustomFieldsManagementPage as CustomFieldsPage; use Relaticle\CustomFields\Models\CustomFieldSection; use Relaticle\CustomFields\Tests\Fixtures\Models\Post; @@ -30,4 +32,19 @@ ->call('setCurrentEntityType', $this->userEntityType) ->assertSee($section->name); }); + + // A phone cannot use a 200px entity rail beside the table, and the browser re-check of the + // stacked layout happens in the host panel. + it('stacks the entity rail above the fields on a narrow viewport', function (): void { + config(['custom-fields.features' => FeatureConfigurator::configure() + ->disable(CustomFieldsFeature::SYSTEM_SECTIONS)]); + + livewire(CustomFieldsPage::class) + ->assertSuccessful() + ->assertSeeHtml('flex flex-col gap-6 md:flex-row') + ->assertSeeHtml('class="md:hidden"') + ->assertSeeHtml('hidden shrink-0 md:block md:min-w-48') + ->assertSeeHtml('fi-tabs fi-vertical') + ->assertSee('Posts'); + }); }); diff --git a/tests/Feature/Admin/Pages/CustomFieldsSectionManagementTest.php b/tests/Feature/Admin/Pages/CustomFieldsSectionManagementTest.php index 0edd9460..2364259e 100644 --- a/tests/Feature/Admin/Pages/CustomFieldsSectionManagementTest.php +++ b/tests/Feature/Admin/Pages/CustomFieldsSectionManagementTest.php @@ -137,6 +137,23 @@ // Assert $component->assertDontSee($section->name); }); + + it('refreshes sections without creating a dynamic property', function (): void { + // Arrange + $section = CustomFieldSection::factory() + ->forEntityType($this->userEntityType) + ->create(); + + $component = livewire(CustomFieldsPage::class) + ->call('setCurrentEntityType', $this->userEntityType); + + // Act + $section->delete(); + $component->call('sectionDeleted'); + + // Assert + expect((new ReflectionObject($component->instance()))->hasProperty('sections'))->toBeFalse(); + }); }); describe('ManageCustomFieldSection - Section Actions', function (): void { diff --git a/tests/Feature/Admin/Pages/DateValidationManagementTest.php b/tests/Feature/Admin/Pages/DateValidationManagementTest.php index dbdd2a9d..6f856366 100644 --- a/tests/Feature/Admin/Pages/DateValidationManagementTest.php +++ b/tests/Feature/Admin/Pages/DateValidationManagementTest.php @@ -6,6 +6,7 @@ use Relaticle\CustomFields\FeatureSystem\FeatureConfigurator; use Relaticle\CustomFields\Livewire\ManageCustomField; use Relaticle\CustomFields\Livewire\ManageCustomFieldSection; +use Relaticle\CustomFields\Livewire\ManageFieldsTable; use Relaticle\CustomFields\Models\CustomField; use Relaticle\CustomFields\Models\CustomFieldSection; use Relaticle\CustomFields\Tests\Fixtures\Models\User; @@ -435,3 +436,45 @@ ]) ->assertHasActionErrors(); }); + +it('strips the preset from a date constraint saved through the flat field table', function (): void { + $field = CustomField::factory() + ->ofType('date') + ->create([ + 'custom_field_section_id' => $this->section->getKey(), + 'entity_type' => $this->entityType, + 'name' => 'Renewal', + 'code' => 'renewal', + ]); + + livewire(ManageFieldsTable::class, ['entityType' => $this->entityType]) + ->callAction('editField', [ + 'name' => 'Renewal', + 'code' => 'renewal', + 'type' => 'date', + 'validation_rules' => [ + 'min_date' => [ + 'preset' => 'today_preset', + 'anchor' => 'today', + 'offset' => 0, + 'offset_unit' => 'days', + 'offset_direction' => 'after', + ], + 'max_date' => [ + 'preset' => 'today_offset', + 'anchor' => 'today', + 'offset' => 30, + 'offset_unit' => 'days', + 'offset_direction' => 'after', + ], + ], + ], ['fieldId' => $field->getKey()]) + ->assertHasNoActionErrors(); + + $rules = $field->refresh()->validation_rules; + + expect($rules->get('min_date'))->toMatchArray(['anchor' => 'today', 'offset' => 0]) + ->and($rules->get('min_date'))->not->toHaveKey('preset') + ->and($rules->get('max_date'))->toMatchArray(['anchor' => 'today', 'offset' => 30]) + ->and($rules->get('max_date'))->not->toHaveKey('preset'); +}); diff --git a/tests/Feature/Admin/Pages/FieldManagementSurfacesTest.php b/tests/Feature/Admin/Pages/FieldManagementSurfacesTest.php new file mode 100644 index 00000000..824294e2 --- /dev/null +++ b/tests/Feature/Admin/Pages/FieldManagementSurfacesTest.php @@ -0,0 +1,311 @@ +actingAs(User::factory()->create()); +}); + +function postFieldsTable(): Testable +{ + return livewire(ManageFieldsTable::class, ['entityType' => Post::class]); +} + +describe('the attribute table', function (): void { + it('gives every active row a reorder handle, a type icon and its badges', function (): void { + CustomField::factory()->ofType('text')->create([ + 'entity_type' => Post::class, + 'name' => 'Account owner', + 'settings' => ['unique_per_entity_type' => true], + 'validation_rules' => ['required' => true], + ]); + + $table = postFieldsTable() + ->assertSeeHtml('x-sortable-handle') + ->assertSee('Account owner') + ->assertSee('Unique'); + + if (! rendersPolished(UiSurface::AttributeTable)) { + // The stock row draws one badge, unique or required, never both. + $table->assertDontSeeHtml('data-surface="attribute-table"') + ->assertDontSeeHtml('fi-cf-attribute-row') + ->assertDontSee('Required'); + + return; + } + + $table->assertSeeHtml('data-surface="attribute-table"') + ->assertSeeHtml('fi-cf-attribute-row') + ->assertSee('Required'); + }); + + it('offers an archived field an activate button without opening the menu', function (): void { + $field = CustomField::factory()->ofType('text')->create([ + 'entity_type' => Post::class, + 'name' => 'Retired field', + ]); + $field->deactivate(); + + postFieldsTable() + ->assertSee('Archived') + ->assertSeeHtml('activateField') + ->callAction('activateField', arguments: ['fieldId' => $field->getKey()]); + + expect($field->fresh()->isActive())->toBeTrue(); + }); + + it('connects the two rows a relationship pairs', function (): void { + $section = sectionForEntity(Post::class); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'mentions', + fromEntityType: Post::class, + toEntityType: Post::class, + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Mentions', sectionId: $section->getKey(), type: RelationshipFieldType::KEY), + toField: new FieldSlotData(name: 'Mentioned By', sectionId: $section->getKey(), type: RelationshipFieldType::KEY), + )); + + $table = postFieldsTable(); + + expect($table->instance()->relationshipPairs())->toHaveCount(2); + + if (! rendersPolished(UiSurface::AttributeTable)) { + $table->assertDontSeeHtml('data-pair="'.$definition->getKey().'"'); + + return; + } + + $table->assertSeeHtml('data-pair="'.$definition->getKey().'"') + ->assertSeeHtml('data-pair-partner="'.$definition->to_field_id.'"') + ->assertSeeHtml('data-pair-partner="'.$definition->from_field_id.'"') + ->assertSee('Paired with Mentioned By on Post') + ->assertSee('Paired with Mentions on Post'); + }); + + it('leaves the pairing sentence readable in a column narrower than it', function (): void { + $section = sectionForEntity(Post::class); + + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'mentions', + fromEntityType: Post::class, + toEntityType: Post::class, + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Mentions', sectionId: $section->getKey(), type: RelationshipFieldType::KEY), + toField: new FieldSlotData(name: 'Mentioned By', sectionId: $section->getKey(), type: RelationshipFieldType::KEY), + )); + + $table = postFieldsTable(); + + if (! rendersPolished(UiSurface::AttributeTable)) { + // The stock row draws no pairing line at all. + $table->assertDontSee('Paired with Mentioned By on Post'); + + return; + } + + $table->assertSeeHtml('title="Paired with Mentioned By on Post"') + ->assertSeeHtml('Paired with Mentioned By on Post'); + }); + + it('leaves a symmetric pairing sentence readable too', function (): void { + $section = sectionForEntity(Post::class); + + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'peers', + fromEntityType: Post::class, + toEntityType: Post::class, + cardinality: RelationshipCardinality::ManyToMany, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Peers', sectionId: $section->getKey(), type: RelationshipFieldType::KEY), + )); + + $table = postFieldsTable(); + + if (! rendersPolished(UiSurface::AttributeTable)) { + $table->assertDontSee('Read from both ends on Post'); + + return; + } + + $table->assertSeeHtml('title="Read from both ends on Post"') + ->assertSeeHtml('Read from both ends on Post'); + }); + + it('says what a custom field is when there are none', function (): void { + $table = postFieldsTable()->assertSee('No custom fields yet'); + + rendersPolished(UiSurface::AttributeTable) + ? $table->assertSee('adds a column of your own to every record') + : $table->assertDontSee('adds a column of your own to every record'); + }); + + it('holds a skeleton for the row that is still loading', function (): void { + CustomField::factory()->ofType('text')->create(['entity_type' => Post::class, 'name' => 'Owner']); + + $table = postFieldsTable(); + + if (! rendersPolished(UiSurface::AttributeTable)) { + $table->assertDontSeeHtml('fi-cf-attribute-skeleton'); + + return; + } + + $table->assertSeeHtml('fi-cf-attribute-skeleton') + ->assertSeeHtml('wire:target="search"'); + }); + + it('reads the relationship pairs in a fixed number of queries however many fields there are', function (): void { + $section = sectionForEntity(Post::class); + + $created = 0; + + $pairQueries = function (int $count) use ($section, &$created): int { + while ($created < $count) { + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'pair_'.$created, + fromEntityType: Post::class, + toEntityType: Post::class, + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Pair '.$created, sectionId: $section->getKey(), type: RelationshipFieldType::KEY), + )); + + $created++; + } + + $component = postFieldsTable()->instance(); + + DB::flushQueryLog(); + DB::enableQueryLog(); + + try { + unset($component->relationshipPairs); + + expect($component->relationshipPairs())->toHaveCount($count); + + return count(DB::getQueryLog()); + } finally { + DB::disableQueryLog(); + DB::flushQueryLog(); + } + }; + + // The first render that has a pair to resolve pays a one-time check, and the two + // flavors reach that render at different points, so the count is read after it. + $pairQueries(2); + + // The two field lists, the definitions, and one eager load for the populated slot. + expect($pairQueries(2))->toBe(4) + ->and($pairQueries(4))->toBe(4); + }); + + it('keeps the pre-redesign table in the native flavor', function (): void { + config()->set('custom-fields.ui.flavor', 'native'); + + CustomField::factory()->ofType('text')->create(['entity_type' => Post::class, 'name' => 'Owner']); + + postFieldsTable() + ->assertDontSeeHtml('data-surface="attribute-table"') + ->assertSee('Owner'); + }); +}); + +describe('the type picker', function (): void { + it('describes every field type the package ships', function (): void { + $choices = containerisedTypeField()->getTypeChoices(); + + expect($choices)->not->toBeEmpty(); + + $missing = array_values(array_filter( + $choices, + static fn (array $choice): bool => $choice['description'] === null, + )); + + expect($missing)->toBeEmpty(implode(', ', array_column($missing, 'key'))); + }); + + it('offers only the types the consumer left on the field, in both flavors', function (string $flavor): void { + config()->set('custom-fields.ui.flavor', $flavor); + + $field = containerisedTypeField(fn (TypeField $field): TypeField => $field + ->options(['text' => 'Text', 'number' => 'Number', 'record' => 'Record']) + ->disableOptionWhen(fn (string $value): bool => $value === 'record')); + + expect(array_column($field->getTypeChoices(), 'key'))->toBe(['text', 'number']) + ->and(array_keys($field->getEnabledOptions()))->toBe(['text', 'number']); + })->with(['polished', 'native']); + + it('renders the grid with a search box, icons and descriptions', function (): void { + $html = renderTypePickerGrid(containerisedTypeField()->getTypeChoices()); + + expect($html) + ->toContain('data-surface="type-picker"') + ->toContain('Search field types') + ->toContain('role="radiogroup"') + ->toContain('A single line of text.') + ->toContain('dark:'); + }); + + it('says the type is locked rather than offering a grid on an existing field', function (): void { + $html = renderTypePickerGrid(containerisedTypeField()->getTypeChoices(), isDisabled: true); + + expect($html) + ->toContain('A field keeps the type it was created with.') + ->not->toContain('Search field types'); + }); +}); + +/** + * A TypeField reads its options through the schema it belongs to, so it is built inside one. + * + * @param ?Closure(TypeField): TypeField $configure + */ +function containerisedTypeField(?Closure $configure = null): TypeField +{ + $field = TypeField::make('type'); + + if ($configure instanceof Closure) { + $field = $configure($field); + } + + $schema = Schema::make(livewire(ManageFieldsTable::class, ['entityType' => Post::class])->instance()) + ->statePath('data') + ->components([$field]); + + // Filament containerises a component when the schema first resolves it, not when it is + // handed over, so the field is read back from the schema rather than from the variable. + $containerised = $schema->getComponent(fn (mixed $component): bool => $component instanceof TypeField, withHidden: true); + + expect($containerised)->toBeInstanceOf(TypeField::class); + + return $containerised; +} + +/** + * @param array $choices + */ +function renderTypePickerGrid(array $choices, bool $isDisabled = false): string +{ + return view('custom-fields::flavors.polished.partials.type-picker-grid', [ + 'choices' => $choices, + 'isDisabled' => $isDisabled, + 'label' => 'Type', + 'stateBinding' => "\$entangle('data.type')", + ])->render(); +} diff --git a/tests/Feature/Commands/MigrateValidationRulesFormatStepTest.php b/tests/Feature/Commands/MigrateValidationRulesFormatStepTest.php deleted file mode 100644 index ed174624..00000000 --- a/tests/Feature/Commands/MigrateValidationRulesFormatStepTest.php +++ /dev/null @@ -1,396 +0,0 @@ -user = User::factory()->create(); - $this->actingAs($this->user); -}); - -function runMigrationStep(): UpgradeStepResult -{ - $step = app(MigrateValidationRulesFormatStep::class); - - $command = new class extends Command - { - protected $name = 'test:noop'; - - /** @var list */ - public array $lines = []; - - public function line($string, $style = null, $verbosity = null): void - { - $this->lines[] = strip_tags((string) $string); - } - }; - - $command->setLaravel(app()); - - return $step->execute(false, $command); -} - -it('converts required rule to new format', function (): void { - $field = CustomField::factory()->create([ - 'type' => 'text', - 'validation_rules' => [['name' => 'required', 'parameters' => []]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('required'))->toBeTrue(); -}); - -it('converts min rule to min_length for text fields', function (): void { - $field = CustomField::factory()->ofType('text')->create([ - 'validation_rules' => [['name' => 'min', 'parameters' => [['value' => '5']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('min_length'))->toBe(5); -}); - -it('converts max rule to max_length for text fields', function (): void { - $field = CustomField::factory()->ofType('textarea')->create([ - 'validation_rules' => [['name' => 'max', 'parameters' => [['value' => '255']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('max_length'))->toBe(255); -}); - -it('converts min rule to min_value for number fields', function (): void { - $field = CustomField::factory()->ofType('number')->create([ - 'validation_rules' => [['name' => 'min', 'parameters' => [['value' => '10']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect((float) $field->validation_rules->get('min_value'))->toBe(10.0); -}); - -it('converts max rule to max_value for currency fields', function (): void { - $field = CustomField::factory()->ofType('currency')->create([ - 'validation_rules' => [['name' => 'max', 'parameters' => [['value' => '999.99']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('max_value'))->toBe(999.99); -}); - -it('converts min rule to min_selections for multi_select fields', function (): void { - $field = CustomField::factory()->ofType('multi_select')->create([ - 'validation_rules' => [['name' => 'min', 'parameters' => [['value' => '2']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('min_selections'))->toBe(2); -}); - -it('converts max rule to max_selections for checkbox_list fields', function (): void { - $field = CustomField::factory()->ofType('checkbox_list')->create([ - 'validation_rules' => [['name' => 'max', 'parameters' => [['value' => '5']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('max_selections'))->toBe(5); -}); - -it('discards after rule with absolute date and warns', function (): void { - $field = CustomField::factory()->ofType('date')->create([ - 'validation_rules' => [['name' => 'after', 'parameters' => [['value' => '2026-01-01']]]], - ]); - - $result = runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules)->toBeNull() - ->and($result->warnings)->toContain(sprintf("Field '%s' (id: %s): Absolute date constraint '2026-01-01' cannot be automatically converted, discarding", $field->name, $field->id)); -}); - -it('converts after rule with today to relative min_date', function (): void { - $field = CustomField::factory()->ofType('date')->create([ - 'validation_rules' => [['name' => 'after', 'parameters' => [['value' => 'today']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - $minDate = $field->validation_rules->get('min_date'); - expect($minDate['anchor'])->toBe('today') - ->and($minDate['offset'])->toBe(0) - ->and($minDate['offset_unit'])->toBe('days') - ->and($minDate['offset_direction'])->toBe('after'); -}); - -it('converts after rule with tomorrow to relative min_date', function (): void { - $field = CustomField::factory()->ofType('date')->create([ - 'validation_rules' => [['name' => 'after', 'parameters' => [['value' => 'tomorrow']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - $minDate = $field->validation_rules->get('min_date'); - expect($minDate['anchor'])->toBe('today') - ->and($minDate['offset'])->toBe(1) - ->and($minDate['offset_unit'])->toBe('days') - ->and($minDate['offset_direction'])->toBe('after'); -}); - -it('converts before rule with yesterday to relative max_date', function (): void { - $field = CustomField::factory()->ofType('date_time')->create([ - 'validation_rules' => [['name' => 'before', 'parameters' => [['value' => 'yesterday']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - $maxDate = $field->validation_rules->get('max_date'); - expect($maxDate['anchor'])->toBe('today') - ->and($maxDate['offset'])->toBe(1) - ->and($maxDate['offset_unit'])->toBe('days') - ->and($maxDate['offset_direction'])->toBe('before'); -}); - -it('discards before_or_equal rule with absolute date and warns', function (): void { - $field = CustomField::factory()->ofType('date')->create([ - 'validation_rules' => [['name' => 'before_or_equal', 'parameters' => [['value' => '2026-12-31']]]], - ]); - - $result = runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules)->toBeNull() - ->and($result->warnings)->toContain(sprintf("Field '%s' (id: %s): Absolute date constraint '2026-12-31' cannot be automatically converted, discarding", $field->name, $field->id)); -}); - -it('converts integer rule to decimal_places 0', function (): void { - $field = CustomField::factory()->ofType('number')->create([ - 'validation_rules' => [['name' => 'integer', 'parameters' => []]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('decimal_places'))->toBe(0); -}); - -it('converts decimal rule to decimal_places', function (): void { - $field = CustomField::factory()->ofType('number')->create([ - 'validation_rules' => [['name' => 'decimal', 'parameters' => [['value' => '2']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('decimal_places'))->toBe(2); -}); - -it('converts mimes rule to accepted_types', function (): void { - $field = CustomField::factory()->ofType('file_upload')->create([ - 'validation_rules' => [['name' => 'mimes', 'parameters' => [['value' => 'pdf'], ['value' => 'doc'], ['value' => 'docx']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('accepted_types'))->toBe(['pdf', 'doc', 'docx']); -}); - -it('converts file max to max_size_kb for file_upload fields', function (): void { - $field = CustomField::factory()->ofType('file_upload')->create([ - 'validation_rules' => [ - ['name' => 'file', 'parameters' => []], - ['name' => 'max', 'parameters' => [['value' => '2048']]], - ], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('max_size_kb'))->toBe(2048); -}); - -it('converts max to max_size_kb when file rule is present even on non-file types', function (): void { - $field = CustomField::factory()->ofType('text')->create([ - 'validation_rules' => [ - ['name' => 'file', 'parameters' => []], - ['name' => 'max', 'parameters' => [['value' => '1024']]], - ], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('max_size_kb'))->toBe(1024); -}); - -it('discards unmappable rules with warning', function (): void { - $field = CustomField::factory()->ofType('text')->create([ - 'validation_rules' => [ - ['name' => 'required', 'parameters' => []], - ['name' => 'alpha', 'parameters' => []], - ], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('required'))->toBeTrue() - ->and($field->validation_rules->has('alpha'))->toBeFalse(); -}); - -it('handles null validation_rules gracefully', function (): void { - CustomField::factory()->create([ - 'type' => 'text', - 'validation_rules' => null, - ]); - - $result = runMigrationStep(); - - expect($result->success)->toBeTrue(); -}); - -it('skips fields already in new format', function (): void { - $field = CustomField::factory()->create([ - 'type' => 'text', - 'validation_rules' => ['required' => true, 'min_length' => 5], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('required'))->toBeTrue() - ->and($field->validation_rules->get('min_length'))->toBe(5); -}); - -it('converts multiple rules at once', function (): void { - $field = CustomField::factory()->ofType('text')->create([ - 'validation_rules' => [ - ['name' => 'required', 'parameters' => []], - ['name' => 'min', 'parameters' => [['value' => '3']]], - ['name' => 'max', 'parameters' => [['value' => '100']]], - ], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('required'))->toBeTrue() - ->and($field->validation_rules->get('min_length'))->toBe(3) - ->and($field->validation_rules->get('max_length'))->toBe(100); -}); - -it('handles empty validation_rules array', function (): void { - CustomField::factory()->create([ - 'type' => 'text', - 'validation_rules' => [], - ]); - - $result = runMigrationStep(); - - expect($result->success)->toBeTrue(); -}); - -it('returns skipped result when no fields need migration', function (): void { - CustomField::factory()->create([ - 'type' => 'text', - 'validation_rules' => ['required' => true], - ]); - - $result = runMigrationStep(); - - expect($result->success)->toBeTrue() - ->and($result->itemsProcessed)->toBe(0) - ->and($result->warnings)->not->toBeEmpty(); -}); - -it('returns warnings for unmappable rules', function (): void { - CustomField::factory()->ofType('text')->create([ - 'validation_rules' => [ - ['name' => 'alpha', 'parameters' => []], - ['name' => 'url', 'parameters' => []], - ], - ]); - - $result = runMigrationStep(); - - expect($result->warnings)->toHaveCount(2); -}); - -it('converts min for tags_input fields to min_selections', function (): void { - $field = CustomField::factory()->ofType('tags_input')->create([ - 'validation_rules' => [['name' => 'min', 'parameters' => [['value' => '1']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('min_selections'))->toBe(1); -}); - -it('converts min for email fields to min_length', function (): void { - $field = CustomField::factory()->ofType('email')->create([ - 'validation_rules' => [['name' => 'min', 'parameters' => [['value' => '5']]]], - ]); - - runMigrationStep(); - - $field->refresh(); - expect($field->validation_rules->get('min_length'))->toBe(5); -}); - -it('can be run via artisan upgrade command with skip', function (): void { - $field = CustomField::factory()->ofType('text')->create([ - 'validation_rules' => [['name' => 'required', 'parameters' => []]], - ]); - - $this->artisan('custom-fields:upgrade', [ - '--force' => true, - '--skip' => 'lookup-fields,email-format,phone-format,clean-multivalue-rules,validate-schema,clear-caches', - ])->assertSuccessful(); - - $field->refresh(); - expect($field->validation_rules->get('required'))->toBeTrue(); -}); - -it('supports dry-run mode without modifying data', function (): void { - $field = CustomField::factory()->ofType('text')->create([ - 'validation_rules' => [['name' => 'required', 'parameters' => []]], - ]); - - $step = app(MigrateValidationRulesFormatStep::class); - - $command = new class extends Command - { - protected $name = 'test:noop'; - - public function line($string, $style = null, $verbosity = null): void {} - }; - $command->setLaravel(app()); - - $result = $step->execute(true, $command); - - $field->refresh(); - - expect($result->itemsProcessed)->toBe(1) - ->and($field->validation_rules->first())->toBe(['name' => 'required', 'parameters' => []]); -}); diff --git a/tests/Feature/Commands/UpgradeCommandTest.php b/tests/Feature/Commands/UpgradeCommandTest.php new file mode 100644 index 00000000..439b83db --- /dev/null +++ b/tests/Feature/Commands/UpgradeCommandTest.php @@ -0,0 +1,84 @@ +user = User::factory()->create(); + $this->actingAs($this->user); +}); + +it('runs every default step in dry-run mode without errors', function (): void { + $this->artisan('custom-fields:upgrade', ['--dry-run' => true]) + ->expectsOutputToContain('Step 1/3: Validate Schema') + ->expectsOutputToContain('Step 2/3: Migrate Record Links') + ->expectsOutputToContain('Step 3/3: Clear Caches') + ->expectsOutput('DRY RUN COMPLETE - No changes were made') + ->assertSuccessful(); +}); + +it('runs every default step when forced', function (): void { + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('Step 1/3: Validate Schema') + ->expectsOutputToContain('Step 2/3: Migrate Record Links') + ->expectsOutputToContain('Step 3/3: Clear Caches') + ->expectsOutput('UPGRADE COMPLETE') + ->assertSuccessful(); +}); + +it('runs the purge only when it is asked for', function (): void { + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('Skipping: purge-record-values') + ->assertSuccessful(); + + $this->artisan('custom-fields:upgrade', ['--force' => true, '--purge' => true]) + ->expectsOutputToContain('Step 3/4: Purge Migrated Record Values') + ->assertSuccessful(); +}); + +it('skips clear-caches when requested', function (): void { + $this->artisan('custom-fields:upgrade', [ + '--force' => true, + '--skip' => 'clear-caches', + ]) + ->expectsOutputToContain('Skipping: clear-caches') + ->assertSuccessful(); +}); + +it('fails with a clear error when --skip names an unknown step', function (): void { + $this->artisan('custom-fields:upgrade', [ + '--force' => true, + '--skip' => 'not-a-real-step', + ]) + ->expectsOutputToContain('Unknown --skip value(s): not-a-real-step.') + ->expectsOutput('Valid steps: validate-schema, migrate-record-links, purge-record-values, clear-caches.') + ->assertFailed(); +}); + +it('rejects a falsy step name in --skip instead of ignoring it', function (): void { + $this->artisan('custom-fields:upgrade', [ + '--force' => true, + '--skip' => '0', + ]) + ->expectsOutputToContain('Unknown --skip value(s): 0.') + ->assertFailed(); +}); + +it('ignores an empty element from a trailing comma in --skip', function (): void { + $this->artisan('custom-fields:upgrade', [ + '--force' => true, + '--skip' => 'clear-caches,', + ]) + ->expectsOutputToContain('Skipping: clear-caches') + ->assertSuccessful(); +}); + +it('does not undercount total steps when --skip repeats the same value', function (): void { + $this->artisan('custom-fields:upgrade', [ + '--force' => true, + '--skip' => 'clear-caches,clear-caches', + ]) + ->expectsOutputToContain('Step 1/2: Validate Schema') + ->assertSuccessful(); +}); diff --git a/tests/Feature/Commands/UpgradeLinksStepTest.php b/tests/Feature/Commands/UpgradeLinksStepTest.php new file mode 100644 index 00000000..d248245e --- /dev/null +++ b/tests/Feature/Commands/UpgradeLinksStepTest.php @@ -0,0 +1,527 @@ + $code, + 'name' => 'Legacy Related', + 'type' => 'record', + 'entity_type' => (new Post)->getMorphClass(), + 'settings' => new CustomFieldSettingsData(allow_multiple: $allowMultiple), + 'custom_field_section_id' => sectionForEntity((new Post)->getMorphClass())->getKey(), + ]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $attributes[config('custom-fields.database.column_names.tenant_foreign_key')] = TenantContextService::getCurrentTenantId(); + } + + return CustomField::factory()->create($attributes); +} + +/** + * A 3.x record field, on the schema a host still has when it runs the upgrade command: the + * migration that drops lookup_type refuses to run until this step has read it. + */ +function legacyRecordField(bool $allowMultiple = true, string $code = 'legacy_related'): CustomField +{ + restoreLookupTypeColumn(); + + $field = recordField($allowMultiple, $code); + + DB::table((string) config('custom-fields.database.table_names.custom_fields')) + ->where('id', $field->getKey()) + ->update(['lookup_type' => (new Post)->getMorphClass()]); + + return $field; +} + +function commitsSchemaChanges(): bool +{ + return DB::connection()->getDriverName() === 'mysql'; +} + +function restoreLookupTypeColumn(): void +{ + $table = (string) config('custom-fields.database.table_names.custom_fields'); + + if (Schema::hasColumn($table, 'lookup_type')) { + return; + } + + Schema::table($table, function (Blueprint $blueprint): void { + $blueprint->string('lookup_type')->nullable(); + }); +} + +function definitionForField(CustomField $field, string $code, RelationshipCardinality $cardinality = RelationshipCardinality::ManyToMany): CustomFieldRelationship +{ + return CustomFieldRelationship::query()->create([ + 'code' => $code, + 'from_entity_type' => (new Post)->getMorphClass(), + 'to_entity_type' => (new Post)->getMorphClass(), + 'cardinality' => $cardinality, + 'is_symmetric' => false, + 'from_field_id' => $field->getKey(), + 'to_field_id' => null, + ]); +} + +it('migrates json_value arrays into definitions and links', function (): void { + $field = legacyRecordField(); + [$first, $second] = Post::factory()->count(2)->create(); + $post = Post::factory()->create(['custom_fields' => [$field->code => [$first->getKey(), $second->getKey()]]]); + + expect(CustomFieldValue::query()->where('custom_field_id', $field->getKey())->count())->toBe(1); + + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('Migrating record field') + ->assertSuccessful(); + + $definition = CustomFieldRelationship::query()->sole(); + $migrated = $field->fresh(); + + expect($definition->code)->toBe($field->code) + ->and($definition->from_field_id)->toEqual($field->getKey()) + ->and($definition->to_field_id)->toBeNull() + ->and($definition->cardinality)->toBe(RelationshipCardinality::ManyToMany) + ->and($definition->from_entity_type)->toBe((new Post)->getMorphClass()) + ->and($definition->to_entity_type)->toBe((new Post)->getMorphClass()) + ->and(CustomFieldLink::query()->active()->pluck('source')->all())->toBe(['migration', 'migration']) + ->and(CustomFieldLink::query()->active()->orderBy('sort_order')->pluck('sort_order')->all())->toBe([0, 1]) + ->and(CustomFieldValue::query()->where('custom_field_id', $field->getKey())->count())->toBe(1) + ->and($post->fresh()->getCustomFieldValue($migrated))->toBe([$first->getKey(), $second->getKey()]); +}); + +it('gives a single-value record field a many to one definition', function (): void { + $field = legacyRecordField(allowMultiple: false); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + expect(CustomFieldRelationship::query()->sole()->cardinality)->toBe(RelationshipCardinality::ManyToOne); +}); + +it('writes no link for a record value that was cleared', function (): void { + $field = legacyRecordField(); + $target = Post::factory()->create(); + $post = Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + $post->update(['custom_fields' => [$field->code => []]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + expect(CustomFieldLink::query()->count())->toBe(0) + ->and(CustomFieldRelationship::query()->count())->toBe(1) + ->and($post->fresh()->getCustomFieldValue($field->fresh()))->toBe([]); +}); + +it('reports the migration in dry-run mode and writes nothing', function (): void { + $field = legacyRecordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--dry-run' => true]) + ->expectsOutputToContain('would be created') + ->assertSuccessful(); + + expect(CustomFieldLink::query()->count())->toBe(0) + ->and(CustomFieldRelationship::query()->count())->toBe(0) + ->and(CustomFieldValue::query()->where('custom_field_id', $field->getKey())->count())->toBe(1); +}); + +it('creates nothing twice across reruns', function (): void { + $field = legacyRecordField(); + [$first, $second] = Post::factory()->count(2)->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$first->getKey(), $second->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + expect(CustomFieldRelationship::query()->count())->toBe(1) + ->and(CustomFieldLink::query()->count())->toBe(2); +}); + +it('migrates the values a field with a definition still holds', function (): void { + $field = legacyRecordField(); + $first = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$first->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + $leftover = Post::factory()->create(); + $second = Post::factory()->create(); + + CustomFieldValue::query()->create([ + 'entity_type' => $leftover->getMorphClass(), + 'entity_id' => $leftover->getKey(), + 'custom_field_id' => $field->getKey(), + 'json_value' => [$second->getKey()], + ]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + expect(CustomFieldRelationship::query()->count())->toBe(1) + ->and(CustomFieldLink::query()->active()->count())->toBe(2) + ->and($leftover->fresh()->getCustomFieldValue($field->fresh()))->toBe([$second->getKey()]); +}); + +it('refuses to migrate a field that reads the far end of its definition', function (): void { + $field = recordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + CustomFieldRelationship::query()->create([ + 'code' => 'reversed_related', + 'from_entity_type' => (new Post)->getMorphClass(), + 'to_entity_type' => (new Post)->getMorphClass(), + 'cardinality' => RelationshipCardinality::ManyToMany, + 'is_symmetric' => false, + 'from_field_id' => null, + 'to_field_id' => $field->getKey(), + ]); + + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('reads the to end of reversed_related, skipped') + ->assertFailed(); + + expect(CustomFieldLink::query()->count())->toBe(0); +}); + +it('keeps the migrated value rows until the purge is asked for', function (): void { + $field = legacyRecordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('Skipping: purge-record-values') + ->assertSuccessful(); + + expect(CustomFieldValue::query()->where('custom_field_id', $field->getKey())->count())->toBe(1); +}); + +it('purges the migrated value rows when asked, leaving the links alone', function (): void { + $field = legacyRecordField(); + $target = Post::factory()->create(); + $post = Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + $this->artisan('custom-fields:upgrade', ['--force' => true, '--purge' => true]) + ->expectsOutputToContain('Purge Migrated Record Values') + ->assertSuccessful(); + + expect(CustomFieldValue::query()->where('custom_field_id', $field->getKey())->count())->toBe(0) + ->and(CustomFieldLink::query()->active()->count())->toBe(1) + ->and($post->fresh()->getCustomFieldValue($field->fresh()))->toBe([$target->getKey()]); +}); + +it('deletes nothing in a dry-run purge', function (): void { + $field = legacyRecordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + $this->artisan('custom-fields:upgrade', ['--dry-run' => true, '--purge' => true]) + ->expectsOutputToContain('would be deleted') + ->assertSuccessful(); + + expect(CustomFieldValue::query()->where('custom_field_id', $field->getKey())->count())->toBe(1); +}); + +it('stops at the gate before a purge that would delete unmigrated values', function (): void { + $field = recordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', [ + '--force' => true, + '--purge' => true, + '--skip' => 'migrate-record-links', + ]) + ->expectsOutputToContain('record links still in json_value') + ->doesntExpectOutputToContain('Purging value rows') + ->assertFailed(); + + expect(CustomFieldValue::query()->where('custom_field_id', $field->getKey())->count())->toBe(1) + ->and(CustomFieldLink::query()->count())->toBe(0); +}); + +it('refuses to purge values the ledger does not hold even when the gate is skipped', function (): void { + $field = recordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + definitionForField($field, 'hand_defined'); + + $this->artisan('custom-fields:upgrade', [ + '--force' => true, + '--purge' => true, + '--skip' => 'migrate-record-links,validate-schema', + ]) + ->expectsOutputToContain('run the Migrate Record Links step first') + ->assertFailed(); + + expect(CustomFieldValue::query()->where('custom_field_id', $field->getKey())->count())->toBe(1); +}); + +it('leaves an unlinked record unlinked across a rerun', function (RelationshipCardinality $cardinality): void { + $field = recordField(); + $target = Post::factory()->create(); + $post = Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + definitionForField($field, 'rerun_related', $cardinality); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + expect(CustomFieldLink::query()->active()->count())->toBe(1); + + $post->update(['custom_fields' => [$field->code => []]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + expect(CustomFieldLink::query()->active()->count())->toBe(0) + ->and(CustomFieldLink::query()->count())->toBe(1); +})->with([ + 'many to many' => RelationshipCardinality::ManyToMany, + 'many to one' => RelationshipCardinality::ManyToOne, +]); + +it('stops before the purge when the migration fails', function (): void { + $field = recordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + CustomFieldRelationship::query()->create([ + 'code' => 'far_end_related', + 'from_entity_type' => (new Post)->getMorphClass(), + 'to_entity_type' => (new Post)->getMorphClass(), + 'cardinality' => RelationshipCardinality::ManyToMany, + 'is_symmetric' => false, + 'from_field_id' => null, + 'to_field_id' => $field->getKey(), + ]); + + $this->artisan('custom-fields:upgrade', ['--force' => true, '--purge' => true]) + ->expectsOutputToContain('Stopping: migrate-record-links failed.') + ->doesntExpectOutputToContain('Purging value rows') + ->assertFailed(); + + expect(CustomFieldValue::query()->where('custom_field_id', $field->getKey())->count())->toBe(1); +}); + +it('warns about record links still in json_value while the run migrates them', function (): void { + $field = legacyRecordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('links still in json_value, migrating below') + ->assertSuccessful(); +}); + +it('fails validation when the record-links step is skipped and links are still in json_value', function (): void { + $field = recordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true, '--skip' => 'migrate-record-links']) + ->expectsOutputToContain('record links still in json_value') + ->assertFailed(); + + expect(CustomFieldLink::query()->count())->toBe(0); +}); + +it('fails validation while the relationship tables are missing', function (): void { + config()->set('custom-fields.database.table_names.custom_field_links', 'not_a_links_table'); + + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('Table not_a_links_table: MISSING') + ->assertFailed(); +}); + +it('validates the schema without the relationship tables while the feature is off', function (): void { + config('custom-fields.features')->disable(CustomFieldsFeature::SYSTEM_RELATIONSHIPS); + config()->set('custom-fields.database.table_names.custom_field_links', 'not_a_links_table'); + + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('Skipping: purge-record-values') + ->assertSuccessful(); +}); + +it('stamps the tenant of the field on the definition and its links', function (): void { + useTenantSchema(9); + + $field = legacyRecordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + expect(CustomFieldRelationship::query()->sole()->tenant_id)->toBe(9) + ->and(CustomFieldLink::query()->sole()->tenant_id)->toBe(9); +})->skip( + fn (): bool => DB::connection()->getDriverName() === 'mysql', + 'MySQL commits DDL implicitly, so the added tenant columns would outlive the test transaction.', +); + +it('leaves every record field with a definition, and no column to hold a target', function (): void { + $field = legacyRecordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + $migration = require __DIR__.'/../../../database/migrations/drop_custom_fields_lookup_type.php'; + $migration->up(); + + $definitionless = CustomField::query() + ->forType('record') + ->get() + ->reject(fn (CustomField $record): bool => $record->relationshipDefinition() instanceof CustomFieldRelationship); + + expect(Schema::hasColumn((string) config('custom-fields.database.table_names.custom_fields'), 'lookup_type'))->toBeFalse() + ->and($definitionless)->toBeEmpty() + ->and(CustomField::query()->forType('record')->count())->toBe(1); +})->skip(commitsSchemaChanges(...), 'MySQL commits DDL implicitly, so dropping the column inside the test would end its transaction.'); + +it('refuses to drop the lookup column while a record field still has no definition', function (): void { + $field = legacyRecordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $migration = require __DIR__.'/../../../database/migrations/drop_custom_fields_lookup_type.php'; + + expect(fn () => $migration->up())->toThrow(RuntimeException::class, $field->code) + ->and(Schema::hasColumn((string) config('custom-fields.database.table_names.custom_fields'), 'lookup_type'))->toBeTrue(); +}); + +it('skips an id whose record is gone and reports it', function (): void { + $field = recordField(); + [$kept, $gone] = Post::factory()->count(2)->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$kept->getKey(), $gone->getKey()]]]); + + definitionForField($field, 'dangling_related'); + $gone->forceDelete(); + + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('1 id(s) point at missing rows, skipped') + ->assertSuccessful(); + + expect(CustomFieldLink::query()->active()->pluck('to_entity_id')->map(intval(...))->all())->toBe([$kept->getKey()]); +}); + +it('migrates an end that is only soft deleted', function (): void { + $field = recordField(); + $trashed = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$trashed->getKey()]]]); + + definitionForField($field, 'trashed_related'); + $trashed->delete(); + + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->doesntExpectOutputToContain('point at missing rows') + ->assertSuccessful(); + + expect(CustomFieldLink::query()->active()->count())->toBe(1); +}); + +it('skips a record field that has neither a target nor values', function (): void { + $field = recordField(); + + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('no lookup type and no values, skipped') + ->assertSuccessful(); + + expect(CustomFieldRelationship::query()->count())->toBe(0) + ->and($field->fresh())->not->toBeNull(); +}); + +it('fails on a record field holding values with no target', function (): void { + $field = recordField(); + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true]) + ->expectsOutputToContain('values with no lookup type') + ->assertFailed(); + + expect(CustomFieldLink::query()->count())->toBe(0); +}); + +it('keeps one definition code per tenant when two tenants share a field code', function (): void { + useTenantSchema(1); + + $first = legacyRecordField(code: 'owner'); + $firstTarget = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$first->code => [$firstTarget->getKey()]]]); + + TenantContextService::setTenantId(2); + + $second = legacyRecordField(code: 'owner'); + $secondTarget = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$second->code => [$secondTarget->getKey()]]]); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + $definitions = CustomFieldRelationship::query() + ->withoutGlobalScopes() + ->orderBy('tenant_id') + ->get(); + + expect($definitions->pluck('code')->all())->toBe(['owner', 'owner']) + ->and($definitions->pluck('tenant_id')->all())->toBe([1, 2]); +})->skip(commitsSchemaChanges(...), 'MySQL commits DDL implicitly, so the added tenant columns would outlive the test transaction.'); + +it('gives a legacy record field with no values a definition of its own', function (): void { + $field = legacyRecordField(); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect($definition->from_field_id)->toEqual($field->getKey()) + ->and($definition->to_entity_type)->toBe((new Post)->getMorphClass()) + ->and(CustomFieldLink::query()->count())->toBe(0); +}); + +it('refuses to drop the lookup column while a valueless record field still has no definition', function (): void { + $field = legacyRecordField(); + + $migration = require __DIR__.'/../../../database/migrations/drop_custom_fields_lookup_type.php'; + + expect(fn () => $migration->up())->toThrow(RuntimeException::class, $field->code); + + $this->artisan('custom-fields:upgrade', ['--force' => true])->assertSuccessful(); + + $migration->up(); + + expect(Schema::hasColumn((string) config('custom-fields.database.table_names.custom_fields'), 'lookup_type'))->toBeFalse() + ->and($field->fresh()->targetEntityType())->toBe((new Post)->getMorphClass()); +})->skip(commitsSchemaChanges(...), 'MySQL commits DDL implicitly, so dropping the column inside the test would end its transaction.'); + +it('drops the lookup column when a record field without a definition has no target either', function (): void { + restoreLookupTypeColumn(); + recordField(); + + $migration = require __DIR__.'/../../../database/migrations/drop_custom_fields_lookup_type.php'; + $migration->up(); + + expect(Schema::hasColumn((string) config('custom-fields.database.table_names.custom_fields'), 'lookup_type'))->toBeFalse(); +})->skip(commitsSchemaChanges(...), 'MySQL commits DDL implicitly, so dropping the column inside the test would end its transaction.'); diff --git a/tests/Feature/ConsumerScopeHooksTest.php b/tests/Feature/ConsumerScopeHooksTest.php index 1c2fdd48..0a96ad20 100644 --- a/tests/Feature/ConsumerScopeHooksTest.php +++ b/tests/Feature/ConsumerScopeHooksTest.php @@ -60,10 +60,10 @@ public function __invoke(string|Component $path = '', bool $isAbsolute = false): function availableFields(VisibilityComponent $component): array { - $method = new ReflectionMethod($component, 'getAvailableFields'); - $method->setAccessible(true); + $property = new ReflectionProperty($component, 'conditionOptions'); + $property->setAccessible(true); - return $method->invoke($component, nullGet()); + return $property->getValue($component)->getAvailableFields(nullGet()); } /** @@ -465,11 +465,12 @@ function (string $entityType, string $type, int|string|null $sectionId) use (&$r beforeEach(function (): void { config()->set('custom-fields.features', FeatureConfigurator::configure() ->enable(CustomFieldsFeature::FIELD_CONDITIONAL_VISIBILITY) + ->disable(CustomFieldsFeature::SYSTEM_SECTIONS) ); collect(Schema::getIndexes('custom_fields')) ->filter(fn (array $index): bool => in_array('custom_field_section_id', $index['columns'], true)) - ->each(fn (array $index) => DB::statement("DROP INDEX \"{$index['name']}\"")); + ->each(fn (array $index) => Schema::table('custom_fields', fn (Blueprint $table) => $table->dropUnique($index['name']))); Schema::table('custom_fields', fn (Blueprint $table) => $table->dropColumn('custom_field_section_id')); }); diff --git a/tests/Feature/DescriptionPositionTest.php b/tests/Feature/DescriptionPositionTest.php index a82e43a5..45f3bc56 100644 --- a/tests/Feature/DescriptionPositionTest.php +++ b/tests/Feature/DescriptionPositionTest.php @@ -23,8 +23,10 @@ expect(DescriptionPosition::ABOVE->getLabel())->toBeString()->not->toBeEmpty(); }); -it('has FIELD_DESCRIPTION_POSITION feature flag disabled by default', function (): void { - expect(FeatureManager::isEnabled(CustomFieldsFeature::FIELD_DESCRIPTION_POSITION))->toBeFalse(); +it('ships the FIELD_DESCRIPTION_POSITION feature flag enabled', function (): void { + config(['custom-fields.features' => shippedFeatureConfigurator()]); + + expect(FeatureManager::isEnabled(CustomFieldsFeature::FIELD_DESCRIPTION_POSITION))->toBeTrue(); }); it('can enable FIELD_DESCRIPTION_POSITION feature flag', function (): void { diff --git a/tests/Feature/FeatureSystemTest.php b/tests/Feature/FeatureSystemTest.php index 499519da..459b5dad 100644 --- a/tests/Feature/FeatureSystemTest.php +++ b/tests/Feature/FeatureSystemTest.php @@ -52,3 +52,77 @@ expect(FeatureManager::isEnabled(CustomFieldsFeature::UI_FIELD_WIDTH_CONTROL))->toBeFalse(); }); + +it('falls back to the package default for a flag the host did not list', function (): void { + config(['custom-fields.features' => FeatureConfigurator::configure()]); + + expect(FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_RELATIONSHIPS))->toBeTrue() + ->and(FeatureManager::isEnabled(CustomFieldsFeature::FIELD_OPTION_COLORS))->toBeTrue() + ->and(FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY))->toBeFalse() + ->and(FeatureManager::isEnabled(CustomFieldsFeature::FIELD_MULTI_VALUE))->toBeFalse(); + + config(['custom-fields.features' => FeatureConfigurator::configure() + ->disable(CustomFieldsFeature::SYSTEM_RELATIONSHIPS) + ->enable(CustomFieldsFeature::FIELD_MULTI_VALUE), + ]); + + expect(FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_RELATIONSHIPS))->toBeFalse() + ->and(FeatureManager::isEnabled(CustomFieldsFeature::FIELD_MULTI_VALUE))->toBeTrue() + ->and(FeatureManager::isEnabled(CustomFieldsFeature::FIELD_OPTION_COLORS))->toBeTrue(); +}); + +it('keeps the package defaults and the shipped config in step', function (): void { + $shipped = shippedFeatureConfigurator(); + + foreach (CustomFieldsFeature::cases() as $feature) { + expect($feature->isEnabledByDefault()) + ->toBe($shipped->isEnabled($feature), $feature->value.' disagrees with the shipped config.'); + } +}); + +it('lists every feature flag explicitly in the shipped config', function (): void { + $shipped = shippedFeatureConfigurator(); + + $configured = (new ReflectionProperty(FeatureConfigurator::class, 'features'))->getValue($shipped); + + $cases = array_map( + fn (CustomFieldsFeature $feature): string => $feature->value, + CustomFieldsFeature::cases(), + ); + + expect(array_keys($configured))->toEqualCanonicalizing($cases); +}); + +it('ships the feature defaults reviewed for 4.0', function (): void { + config(['custom-fields.features' => shippedFeatureConfigurator()]); + + $actual = []; + + foreach (CustomFieldsFeature::cases() as $feature) { + $actual[$feature->value] = FeatureManager::isEnabled($feature); + } + + expect($actual)->toEqual([ + CustomFieldsFeature::FIELD_CONDITIONAL_VISIBILITY->value => true, + CustomFieldsFeature::FIELD_ENCRYPTION->value => true, + CustomFieldsFeature::FIELD_OPTION_COLORS->value => true, + CustomFieldsFeature::FIELD_CODE_AUTO_GENERATE->value => false, + CustomFieldsFeature::FIELD_MULTI_VALUE->value => false, + CustomFieldsFeature::FIELD_UNIQUE_VALUE->value => false, + CustomFieldsFeature::FIELD_VALIDATION_RULES->value => true, + CustomFieldsFeature::FIELD_DESCRIPTION->value => true, + CustomFieldsFeature::FIELD_DESCRIPTION_POSITION->value => true, + CustomFieldsFeature::MODEL_ATTRIBUTE_CONDITIONS->value => false, + CustomFieldsFeature::SECTION_CONDITIONAL_VISIBILITY->value => true, + CustomFieldsFeature::UI_TABLE_COLUMNS->value => true, + CustomFieldsFeature::UI_TABLE_FILTERS->value => true, + CustomFieldsFeature::UI_TOGGLEABLE_COLUMNS->value => true, + CustomFieldsFeature::UI_TOGGLEABLE_COLUMNS_HIDDEN_DEFAULT->value => false, + CustomFieldsFeature::UI_FIELD_WIDTH_CONTROL->value => true, + CustomFieldsFeature::UI_SECTION_WIDTH_CONTROL->value => true, + CustomFieldsFeature::SYSTEM_MANAGEMENT_INTERFACE->value => true, + CustomFieldsFeature::SYSTEM_MULTI_TENANCY->value => false, + CustomFieldsFeature::SYSTEM_SECTIONS->value => true, + CustomFieldsFeature::SYSTEM_RELATIONSHIPS->value => true, + ]); +}); diff --git a/tests/Feature/Filament/Components/RecordSelectOrderingTest.php b/tests/Feature/Filament/Components/RecordSelectOrderingTest.php index f7e68d6d..8ae1e4c9 100644 --- a/tests/Feature/Filament/Components/RecordSelectOrderingTest.php +++ b/tests/Feature/Filament/Components/RecordSelectOrderingTest.php @@ -32,7 +32,7 @@ }); it('orders by a configured column, most recently updated first', function (): void { - config()->set('custom-fields.selects.record_lookup.order_column', 'updated_at'); + config()->set('custom-fields.selects.record.order_column', 'updated_at'); makeLookupRecord('Oldest', Carbon::parse('2020-01-01')); makeLookupRecord('Newest', Carbon::parse('2026-01-01')); @@ -43,7 +43,7 @@ }); it('breaks ties on the model key when a configured column repeats', function (): void { - config()->set('custom-fields.selects.record_lookup.order_column', 'updated_at'); + config()->set('custom-fields.selects.record.order_column', 'updated_at'); $sameMoment = Carbon::parse('2024-05-01 09:00:00'); @@ -60,7 +60,7 @@ }); it('falls back to the model key when a configured updated_at model has no timestamps', function (): void { - config()->set('custom-fields.selects.record_lookup.order_column', 'updated_at'); + config()->set('custom-fields.selects.record.order_column', 'updated_at'); registerLookupEntity(TimestamplessTag::class, primaryAttribute: 'name'); @@ -73,8 +73,8 @@ }); it('honours a configured order column and direction', function (): void { - config()->set('custom-fields.selects.record_lookup.order_column', 'title'); - config()->set('custom-fields.selects.record_lookup.order_direction', 'asc'); + config()->set('custom-fields.selects.record.order_column', 'title'); + config()->set('custom-fields.selects.record.order_direction', 'asc'); makeLookupRecord('Charlie'); makeLookupRecord('Alpha'); @@ -85,7 +85,7 @@ }); it('applies the configured limit', function (): void { - config()->set('custom-fields.selects.record_lookup.limit', 2); + config()->set('custom-fields.selects.record.limit', 2); $ids = array_map( fn (int $i): string => (string) makeLookupRecord('Record '.$i)->getKey(), diff --git a/tests/Feature/Filament/Components/RecordSelectSearchTest.php b/tests/Feature/Filament/Components/RecordSelectSearchTest.php index fdf6f31f..984b41b3 100644 --- a/tests/Feature/Filament/Components/RecordSelectSearchTest.php +++ b/tests/Feature/Filament/Components/RecordSelectSearchTest.php @@ -2,8 +2,11 @@ declare(strict_types=1); -use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Data\FieldSlotData; +use Relaticle\CustomFields\Data\RelationshipDefinitionData; +use Relaticle\CustomFields\Enums\RelationshipCardinality; use Relaticle\CustomFields\Models\CustomFieldSection; +use Relaticle\CustomFields\Services\Relationships\CreateRelationshipDefinition; use Relaticle\CustomFields\Tests\Fixtures\Models\Post; use Relaticle\CustomFields\Tests\Fixtures\Resources\Posts\Pages\EditPost; @@ -31,7 +34,7 @@ }); it('honours a configured minimum search length', function (): void { - config()->set('custom-fields.selects.record_lookup.min_search_length', 3); + config()->set('custom-fields.selects.record.min_search_length', 3); makeLookupRecord('Acme Industries'); makeLookupRecord('Zenith Corp'); @@ -39,19 +42,42 @@ expect(recordSelectSearch('Ac'))->toHaveCount(2); }); + it('splits the term and matches the words across attributes', function (): void { + registerLookupEntity(Post::class, primaryAttribute: 'title', searchAttributes: ['title', 'content']); + + Post::factory()->create(['title' => 'Jane Industries', 'content' => 'Founded by Doe']); + Post::factory()->create(['title' => 'Zenith Corp', 'content' => 'Nothing to see']); + + expect(array_column(recordSelectSearch('Jane Doe'), 'label'))->toBe(['Jane Industries']); + }); + + it('matches a lookup record whatever the case of the term', function (): void { + makeLookupRecord('Acme Industries'); + + expect(array_column(recordSelectSearch('ACME'), 'label'))->toBe(['Acme Industries']); + }); + + it('accepts a nested group of search attributes', function (): void { + registerLookupEntity(Post::class, primaryAttribute: 'title', searchAttributes: [['title', 'content']]); + + Post::factory()->create(['title' => 'Acme Industries', 'content' => 'Nothing to see']); + Post::factory()->create(['title' => 'Zenith Corp', 'content' => 'Nothing to see']); + + expect(array_column(recordSelectSearch('Acme'), 'label'))->toBe(['Acme Industries']); + }); + it('hands the configured minimum to the rendered field', function (): void { - config()->set('custom-fields.selects.record_lookup.min_search_length', 3); + config()->set('custom-fields.selects.record.min_search_length', 3); $section = CustomFieldSection::factory()->forEntityType(Post::class)->create(); - CustomField::factory()->create([ - 'code' => 'related_post', - 'name' => 'Related Post', - 'type' => 'record', - 'entity_type' => Post::class, - 'lookup_type' => Post::class, - 'custom_field_section_id' => $section->getKey(), - ]); + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_post', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Related Post', sectionId: $section->getKey()), + )); livewire(EditPost::class, ['record' => makeLookupRecord('Acme Industries')->getRouteKey()]) ->assertSee('minSearchLength: 3', escape: false) diff --git a/tests/Feature/Filament/Components/SelectConfigTest.php b/tests/Feature/Filament/Components/SelectConfigTest.php index ffc19aa2..53fa334f 100644 --- a/tests/Feature/Filament/Components/SelectConfigTest.php +++ b/tests/Feature/Filament/Components/SelectConfigTest.php @@ -4,8 +4,8 @@ it('exposes select defaults that preserve documented behavior', function (): void { expect(config('custom-fields.selects.searchable_threshold'))->toBe(10) - ->and(config('custom-fields.selects.record_lookup.order_column'))->toBeNull() - ->and(config('custom-fields.selects.record_lookup.order_direction'))->toBe('desc') - ->and(config('custom-fields.selects.record_lookup.limit'))->toBe(50) - ->and(config('custom-fields.selects.record_lookup.min_search_length'))->toBe(2); + ->and(config('custom-fields.selects.record.order_column'))->toBeNull() + ->and(config('custom-fields.selects.record.order_direction'))->toBe('desc') + ->and(config('custom-fields.selects.record.limit'))->toBe(50) + ->and(config('custom-fields.selects.record.min_search_length'))->toBe(2); }); diff --git a/tests/Feature/Imports/ImportContractConformanceTest.php b/tests/Feature/Imports/ImportContractConformanceTest.php index cd3ba4a0..70129534 100644 --- a/tests/Feature/Imports/ImportContractConformanceTest.php +++ b/tests/Feature/Imports/ImportContractConformanceTest.php @@ -115,8 +115,11 @@ function conformanceFieldTypes(): array foreach (conformanceFieldTypes() as $type) { $state = conformanceCast($type, ''); + if ($state === null) { + continue; + } - if ($state === null || $state === []) { + if ($state === []) { continue; } diff --git a/tests/Feature/Imports/ImportDateFormatTest.php b/tests/Feature/Imports/ImportDateFormatTest.php index d2053672..981f9ff9 100644 --- a/tests/Feature/Imports/ImportDateFormatTest.php +++ b/tests/Feature/Imports/ImportDateFormatTest.php @@ -50,7 +50,7 @@ it('parses every example it advertises', function (ImportDateFormat $format, bool $withTime): void { foreach ($format->getExamples($withTime) as $example) { expect($format->parse($example, $withTime)) - ->not->toBeNull("{$format->value} advertises '{$example}' but cannot parse it"); + ->not->toBeNull(sprintf("%s advertises '%s' but cannot parse it", $format->value, $example)); } })->with([ 'iso date' => [ImportDateFormat::ISO, false], diff --git a/tests/Feature/Imports/ImportValidationContractTest.php b/tests/Feature/Imports/ImportValidationContractTest.php index e040839f..4e1ec2e1 100644 --- a/tests/Feature/Imports/ImportValidationContractTest.php +++ b/tests/Feature/Imports/ImportValidationContractTest.php @@ -6,8 +6,13 @@ use Filament\Actions\Imports\Models\Import; use Illuminate\Support\Facades\Schema; use Illuminate\Validation\ValidationException; +use Relaticle\CustomFields\Data\FieldSlotData; +use Relaticle\CustomFields\Data\RelationshipDefinitionData; +use Relaticle\CustomFields\Enums\RelationshipCardinality; use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldLink; use Relaticle\CustomFields\Models\CustomFieldSection; +use Relaticle\CustomFields\Services\Relationships\CreateRelationshipDefinition; use Relaticle\CustomFields\Tests\Fixtures\Imports\PostImporter; use Relaticle\CustomFields\Tests\Fixtures\Models\Post; use Relaticle\CustomFields\Tests\Fixtures\Models\User; @@ -275,3 +280,53 @@ function runPostImport(array $rows): array 'custom_fields_ra_eligible' => 'Q/A', ]); }); + +it('accepts an unchanged single-end record when the import updates a record', function (): void { + registerPostLookupEntity(); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'import_ownership', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::OneToOne, + fromField: new FieldSlotData(name: 'Owner', sectionId: test()->section->getKey()), + )); + + $code = $definition->fromField->code; + $target = Post::factory()->create(['title' => 'Owned Post']); + Post::factory()->create(['title' => 'Smith household', 'custom_fields' => [$code => [$target->getKey()]]]); + + $result = runPostImport([[ + 'title' => 'Smith household', + 'custom_fields_'.$code => (string) $target->getKey(), + ]]); + + expect($result['failures'])->toBe([]) + ->and($result['imported'])->toBe(1) + ->and(CustomFieldLink::query()->active()->count())->toBe(1); +}); + +it('still reports a record another record holds on an update import', function (): void { + registerPostLookupEntity(); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'import_ownership', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::OneToOne, + fromField: new FieldSlotData(name: 'Owner', sectionId: test()->section->getKey()), + )); + + $code = $definition->fromField->code; + $target = Post::factory()->create(['title' => 'Owned Post']); + Post::factory()->create(['title' => 'First Owner', 'custom_fields' => [$code => [$target->getKey()]]]); + Post::factory()->create(['title' => 'Smith household']); + + $result = runPostImport([[ + 'title' => 'Smith household', + 'custom_fields_'.$code => (string) $target->getKey(), + ]]); + + expect($result['failures'])->toHaveCount(1) + ->and($result['failures'][0])->toContain('is already linked to'); +}); diff --git a/tests/Feature/Imports/RichEditorImportTransformerTest.php b/tests/Feature/Imports/RichEditorImportTransformerTest.php new file mode 100644 index 00000000..81f18536 --- /dev/null +++ b/tests/Feature/Imports/RichEditorImportTransformerTest.php @@ -0,0 +1,26 @@ +transformer = $fieldType->configure()->getImportTransformer(); +}); + +it('throws instead of returning false when the array state cannot be encoded as JSON', function (): void { + expect(fn () => ($this->transformer)(["\xB1\x31"]))->toThrow(RuntimeException::class); +}); + +it('encodes a valid array state as JSON', function (): void { + $result = ($this->transformer)(['value' => 'ok']); + + expect($result)->toBe('{"value":"ok"}'); +}); + +it('wraps plain text lines in paragraph tags', function (): void { + $result = ($this->transformer)("line one\nline two"); + + expect($result)->toBe('

line one

line two

'); +}); diff --git a/tests/Feature/Integration/AbstractComponentFactoryClosureTest.php b/tests/Feature/Integration/AbstractComponentFactoryClosureTest.php new file mode 100644 index 00000000..25d24242 --- /dev/null +++ b/tests/Feature/Integration/AbstractComponentFactoryClosureTest.php @@ -0,0 +1,57 @@ +createComponent($customField, 'form_component', FormComponentInterface::class); + } +} + +class ClosureFormComponentFieldType extends BaseFieldType +{ + public function configure(): FieldSchema + { + return FieldSchema::text() + ->key('closure-probe-type') + ->label('Closure Probe Type') + ->icon('heroicon-o-pencil') + ->formComponent(fn (CustomField $customField): Field => TextInput::make($customField->getFieldName())); + } +} + +it('raises a clear error instead of a TypeError when createComponent() receives a Closure', function (): void { + CustomFieldsType::register([ + 'closure-probe-type' => ClosureFormComponentFieldType::class, + ]); + + $section = CustomFieldSection::factory()->create([ + 'name' => 'Closure Probe Section', + 'entity_type' => Post::class, + 'active' => true, + ]); + + $field = CustomField::factory()->create([ + 'custom_field_section_id' => $section->id, + 'name' => 'Closure Field', + 'code' => 'closure_field', + 'type' => 'closure-probe-type', + ]); + + $factory = app(ClosureProbeFactory::class); + + expect(fn () => $factory->probe($field))->toThrow(InvalidArgumentException::class, 'resolved to a Closure'); +}); diff --git a/tests/Feature/Integration/Builders/ThroughColumnsTest.php b/tests/Feature/Integration/Builders/ThroughColumnsTest.php new file mode 100644 index 00000000..930d8da1 --- /dev/null +++ b/tests/Feature/Integration/Builders/ThroughColumnsTest.php @@ -0,0 +1,397 @@ + + */ +function throughTableOrder(string $direction): array +{ + $records = throughTable(Comment::class, Post::class, 'post') + ->sortTable('custom_fields.category', $direction) + ->instance() + ->getTableRecords(); + + return $records + ->map(fn (Comment $comment): int => (int) $comment->getKey()) + ->values() + ->all(); +} + +function commentOnPostWith(CustomField $field, string $value): Comment +{ + $post = Post::factory()->create(); + $post->saveCustomFieldValue($field, $value); + + return Comment::factory()->create(['post_id' => $post->getKey()]); +} + +it('reads the related record field as column state', function (): void { + $field = throughTextField(Post::class, 'category', 'Category'); + $comment = commentOnPostWith($field, 'Technology'); + + throughTable(Comment::class, Post::class, 'post') + ->assertTableColumnExists('custom_fields.category') + ->assertCanSeeTableRecords([$comment]) + ->assertTableColumnStateSet('custom_fields.category', 'Technology', $comment); +}); + +it('renders empty when the row has no related record', function (): void { + throughTextField(Post::class, 'category', 'Category'); + + $orphan = Comment::factory()->create(['post_id' => Post::query()->max('id') + 1000]); + + throughTable(Comment::class, Post::class, 'post') + ->assertCanSeeTableRecords([$orphan]) + ->assertTableColumnStateSet('custom_fields.category', null, $orphan) + ->assertTableColumnFormattedStateSet('custom_fields.category', null, $orphan); +}); + +it('sorts rows by the related field through a belongs-to relation', function (): void { + $field = throughTextField(Post::class, 'category', 'Category'); + + $bravo = commentOnPostWith($field, 'Bravo'); + $alpha = commentOnPostWith($field, 'Alpha'); + $charlie = commentOnPostWith($field, 'Charlie'); + + throughTable(Comment::class, Post::class, 'post') + ->sortTable('custom_fields.category', 'asc') + ->assertCanSeeTableRecords([$alpha, $bravo, $charlie], inOrder: true) + ->sortTable('custom_fields.category', 'desc') + ->assertCanSeeTableRecords([$charlie, $bravo, $alpha], inOrder: true); +}); + +it('sorts rows by the related field through a has-one relation', function (): void { + $field = throughTextField(Post::class, 'category', 'Category'); + + $authors = collect(['Bravo', 'Alpha', 'Charlie'])->map(function (string $value) use ($field): User { + $author = User::factory()->create(); + + Post::factory()->create(['author_id' => $author->getKey()])->saveCustomFieldValue($field, $value); + + return $author; + }); + + [$bravo, $alpha, $charlie] = $authors->all(); + + throughTable(fn (): Builder => User::query()->whereKey($authors->map(fn (User $author): int => (int) $author->getKey())->all()), Post::class, 'post') + ->sortTable('custom_fields.category', 'asc') + ->assertCanSeeTableRecords([$alpha, $bravo, $charlie], inOrder: true) + ->sortTable('custom_fields.category', 'desc') + ->assertCanSeeTableRecords([$charlie, $bravo, $alpha], inOrder: true); +}); + +it('sorts rows by the related field through a morph-one relation', function (): void { + $field = throughTextField(Comment::class, 'sentiment', 'Sentiment'); + + $posts = collect(['Bravo', 'Alpha', 'Charlie'])->map(function (string $value) use ($field): Post { + $post = Post::factory()->create(); + + $comment = Comment::factory()->create([ + 'post_id' => $post->getKey(), + 'commentable_type' => $post->getMorphClass(), + 'commentable_id' => $post->getKey(), + ]); + + $comment->saveCustomFieldValue($field, $value); + + return $post; + }); + + [$bravo, $alpha, $charlie] = $posts->all(); + + throughTable(fn (): Builder => Post::query()->whereKey($posts->map(fn (Post $post): int => (int) $post->getKey())->all()), Comment::class, 'featuredComment') + ->sortTable('custom_fields.sentiment', 'asc') + ->assertCanSeeTableRecords([$alpha, $bravo, $charlie], inOrder: true) + ->sortTable('custom_fields.sentiment', 'desc') + ->assertCanSeeTableRecords([$charlie, $bravo, $alpha], inOrder: true); +}); + +it('searches rows by the related record field', function (): void { + $field = throughTextField(Post::class, 'category', 'Category', searchable: true); + + $match = commentOnPostWith($field, 'Technology'); + $other = commentOnPostWith($field, 'Science'); + + throughTable(Comment::class, Post::class, 'post') + ->searchTable('Technology') + ->assertCanSeeTableRecords([$match]) + ->assertCanNotSeeTableRecords([$other]); +}); + +it('evaluates a visibility condition against the related record', function (): void { + $status = throughTextField(Post::class, 'status', 'Status'); + $priority = throughTextField(Post::class, 'priority', 'Priority', visibility: new VisibilityData( + mode: VisibilityMode::SHOW_WHEN, + logic: VisibilityLogic::ALL, + conditions: new DataCollection(VisibilityConditionData::class, [ + new VisibilityConditionData( + field_code: 'status', + operator: VisibilityOperator::EQUALS, + value: 'published', + ), + ]), + )); + + $published = Post::factory()->create(); + $published->saveCustomFieldValue($status, 'published'); + $published->saveCustomFieldValue($priority, 'high'); + + $draft = Post::factory()->create(); + $draft->saveCustomFieldValue($status, 'draft'); + $draft->saveCustomFieldValue($priority, 'low'); + + $onPublished = Comment::factory()->create(['post_id' => $published->getKey()]); + $onDraft = Comment::factory()->create(['post_id' => $draft->getKey()]); + + throughTable(Comment::class, Post::class, 'post') + ->assertTableColumnFormattedStateSet('custom_fields.priority', 'high', $onPublished) + ->assertTableColumnFormattedStateSet('custom_fields.priority', null, $onDraft); +}); + +it('shows a record field through a relation but leaves it unsortable', function (): void { + registerPostLookupEntity(); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'through_related_post', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Related Post', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + )); + + $code = $definition->fromField->code; + + $linked = Post::factory()->create(['title' => 'Linked Post']); + $post = Post::factory()->create(['custom_fields' => [$code => [$linked->getKey()]]]); + $comment = Comment::factory()->create(['post_id' => $post->getKey()]); + + throughTable(Comment::class, Post::class, 'post') + ->assertCanSeeTableRecords([$comment]) + ->assertTableColumnExists('custom_fields.'.$code, fn (Column $column): bool => ! $column->isSortable()) + ->assertSee('Linked Post'); +}); + +it('sorts rows whose related record is missing or trashed last in both directions', function (): void { + $field = throughTextField(Post::class, 'category', 'Category'); + + $alpha = commentOnPostWith($field, 'Alpha'); + $bravo = commentOnPostWith($field, 'Bravo'); + + $trashedPost = Post::factory()->create(); + $trashedPost->saveCustomFieldValue($field, 'Aaa Trashed'); + + $onTrashed = Comment::factory()->create(['post_id' => $trashedPost->getKey()]); + $trashedPost->delete(); + + $orphan = Comment::factory()->create(['post_id' => Post::query()->withTrashed()->max('id') + 1000]); + + throughTable(Comment::class, Post::class, 'post') + ->assertTableColumnStateSet('custom_fields.category', null, $onTrashed) + ->assertTableColumnStateSet('custom_fields.category', null, $orphan); + + $unrelated = [(int) $onTrashed->getKey(), (int) $orphan->getKey()]; + + $ascending = throughTableOrder('asc'); + $descending = throughTableOrder('desc'); + + expect(array_slice($ascending, 0, 2))->toBe([(int) $alpha->getKey(), (int) $bravo->getKey()]) + ->and(array_slice($ascending, 2))->toEqualCanonicalizing($unrelated) + ->and(array_slice($descending, 0, 2))->toBe([(int) $bravo->getKey(), (int) $alpha->getKey()]) + ->and(array_slice($descending, 2))->toEqualCanonicalizing($unrelated); +}); + +it('sorts through a constrained has-one by the child the relation admits', function (): void { + $field = throughTextField(Post::class, 'category', 'Category'); + + $authors = collect(['Bravo', 'Alpha', 'Charlie'])->map(function (string $value) use ($field): User { + $author = User::factory()->create(); + + Post::factory()->create(['author_id' => $author->getKey(), 'is_published' => false]) + ->saveCustomFieldValue($field, 'Zzz Draft'); + + Post::factory()->create(['author_id' => $author->getKey(), 'is_published' => true]) + ->saveCustomFieldValue($field, $value); + + return $author; + }); + + [$bravo, $alpha, $charlie] = $authors->all(); + $keys = $authors->map(fn (User $author): int => (int) $author->getKey())->all(); + + throughTable(fn (): Builder => User::query()->whereKey($keys), Post::class, 'publishedPost') + ->sortTable('custom_fields.category', 'asc') + ->assertCanSeeTableRecords([$alpha, $bravo, $charlie], inOrder: true) + ->sortTable('custom_fields.category', 'desc') + ->assertCanSeeTableRecords([$charlie, $bravo, $alpha], inOrder: true); +}); + +function recordFieldOnPost(string $code, CustomFieldSettingsData $settings): CustomField +{ + registerPostLookupEntity(); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: $code, + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Related Post', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + )); + + $definition->fromField->update(['settings' => $settings]); + + return $definition->fromField->refresh(); +} + +function throughColumnFor(string $relation, string $code): Column +{ + return CustomFields::table() + ->forModel(Post::class) + ->through($relation) + ->columns() + ->first(fn (Column $column): bool => $column->getName() === 'custom_fields.'.$code); +} + +/** + * The root cause of rendering a table whose through path the row model cannot serve. + */ +function throughRenderFailure(string $modelClass, string $relation): ?Throwable +{ + $thrown = null; + + try { + throughTable($modelClass, Post::class, $relation)->assertSuccessful(); + } catch (Throwable $throwable) { + $thrown = $throwable; + } + + while ($thrown?->getPrevious() instanceof Throwable) { + $thrown = $thrown->getPrevious(); + } + + return $thrown; +} + +it('rejects an unsupported through path from the column entry', function (string $modelClass, string $relation, string $reason): void { + throughTextField(Post::class, 'category', 'Category'); + + $modelClass::factory()->create(); + + $thrown = throughRenderFailure($modelClass, $relation); + + expect($thrown)->toBeInstanceOf(UnsupportedThroughRelationException::class) + ->and($thrown->getMessage())->toContain($reason); +})->with([ + 'missing relation' => [Comment::class, 'publisher', 'has no relation named'], + 'polymorphic to-one' => [Comment::class, 'commentable', 'is a MorphTo'], + 'has many' => [User::class, 'posts', 'is a HasMany'], + 'belongs to many' => [Post::class, 'tagModels', 'is a BelongsToMany'], + 'target without custom fields' => [Post::class, 'author', 'does not implement HasCustomFields'], +]); + +it('rejects an unsupported through path from the record column entry', function (string $modelClass, string $relation, string $reason): void { + $field = recordFieldOnPost('entry_guard', new CustomFieldSettingsData); + + $record = $modelClass::factory()->create(); + + // Built without the builder, so the cell's own hop is what answers, not the gate the + // builder installs in front of it. + $column = app(RecordColumn::class)->make($field)->through($relation); + + expect($column)->toBeInstanceOf(RecordColumnView::class) + ->and(fn (): array => $column->getRecords($record)) + ->toThrow(UnsupportedThroughRelationException::class, $reason); +})->with([ + 'missing relation' => [Comment::class, 'publisher', 'has no relation named'], + 'polymorphic to-one' => [Comment::class, 'commentable', 'is a MorphTo'], + 'has many' => [User::class, 'posts', 'is a HasMany'], +]); + +it('searches a record field through a relation by the linked record, not the stored value', function (): void { + $field = recordFieldOnPost('searchable_link', new CustomFieldSettingsData(searchable: true)); + + $target = Post::factory()->create(['title' => 'Findable Target']); + $linking = Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + + $onLinking = Comment::factory()->create(['post_id' => $linking->getKey()]); + $onUnlinked = Comment::factory()->create(['post_id' => Post::factory()->create()->getKey()]); + + throughTable(Comment::class, Post::class, 'post') + ->searchTable('Findable') + ->assertCanSeeTableRecords([$onLinking]) + ->assertCanNotSeeTableRecords([$onUnlinked]); +}); + +it('hides a conditionally hidden record column per record on both paths', function (): void { + $status = throughTextField(Post::class, 'status', 'Status'); + + $field = recordFieldOnPost('conditional_link', new CustomFieldSettingsData( + visibility: new VisibilityData( + mode: VisibilityMode::SHOW_WHEN, + logic: VisibilityLogic::ALL, + conditions: new DataCollection(VisibilityConditionData::class, [ + new VisibilityConditionData(field_code: 'status', operator: VisibilityOperator::EQUALS, value: 'published'), + ]), + ), + )); + + $target = Post::factory()->create(['title' => 'Linked Target']); + + $published = Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + $published->saveCustomFieldValue($status, 'published'); + + $draft = Post::factory()->create(['custom_fields' => [$field->code => [$target->getKey()]]]); + $draft->saveCustomFieldValue($status, 'draft'); + + $direct = CustomFields::table() + ->forModel(Post::class) + ->columns() + ->first(fn (Column $column): bool => $column->getName() === 'custom_fields.'.$field->code); + + expect($direct->getRecords($published))->toHaveCount(1) + ->and($direct->getRecords($draft))->toBe([]); + + $onPublished = Comment::factory()->create(['post_id' => $published->getKey()]); + $onDraft = Comment::factory()->create(['post_id' => $draft->getKey()]); + + $through = throughColumnFor('post', $field->code); + + expect($through->getRecords($onPublished))->toHaveCount(1) + ->and($through->getRecords($onDraft))->toBe([]); + + throughTable(fn (): Builder => Comment::query()->whereKey($onDraft->getKey()), Post::class, 'post') + ->assertCanSeeTableRecords([$onDraft]) + ->assertDontSee('Linked Target'); + + throughTable(fn (): Builder => Comment::query()->whereKey($onPublished->getKey()), Post::class, 'post') + ->assertSee('Linked Target'); +}); diff --git a/tests/Feature/Integration/Builders/ThroughEagerLoadingTest.php b/tests/Feature/Integration/Builders/ThroughEagerLoadingTest.php new file mode 100644 index 00000000..d7261e00 --- /dev/null +++ b/tests/Feature/Integration/Builders/ThroughEagerLoadingTest.php @@ -0,0 +1,93 @@ +create(); + $post->saveCustomFieldValue($field, 'Value '.$index); + + Comment::factory()->create(['post_id' => $post->getKey()]); + } +} + +function valueQueryCount(int $rows, CustomField $field): int +{ + Comment::query()->delete(); + Post::query()->forceDelete(); + + commentsOnPostsWith($field, $rows); + + DB::flushQueryLog(); + DB::enableQueryLog(); + + try { + throughTable( + Comment::class, + Post::class, + 'post', + fn (Table $table): Table => $table->modifyQueryUsing( + fn (Builder $query): Builder => $query->with('post.customFieldValues.customField') + ), + )->assertCountTableRecords($rows); + + return count(array_filter( + DB::getQueryLog(), + static fn (array $entry): bool => str_contains($entry['query'], 'custom_field_values'), + )); + } finally { + DB::disableQueryLog(); + DB::flushQueryLog(); + } +} + +it('reads the related fields in a fixed number of queries when the host eager loads', function (): void { + $field = throughTextField(Post::class, 'category', 'Category'); + + $forTwoRows = valueQueryCount(2, $field); + $forSixRows = valueQueryCount(6, $field); + + expect($forSixRows)->toBe($forTwoRows); +}); + +it('raises the lazy load violation, not a silent n+1, when the host does not eager load', function (): void { + $field = throughTextField(Post::class, 'category', 'Category'); + + commentsOnPostsWith($field, 3); + + Model::preventLazyLoading(); + + $thrown = null; + + try { + throughTable(Comment::class, Post::class, 'post')->assertCountTableRecords(3); + } catch (Throwable $throwable) { + $thrown = $throwable; + } finally { + Model::preventLazyLoading(false); + } + + while ($thrown?->getPrevious() instanceof Throwable) { + $thrown = $thrown->getPrevious(); + } + + expect($thrown)->toBeInstanceOf(LazyLoadingViolationException::class) + ->and($thrown->getMessage())->toContain('[post]') + ->and($thrown->getMessage())->toContain(Comment::class); +}); diff --git a/tests/Feature/Integration/Builders/ThroughFiltersTest.php b/tests/Feature/Integration/Builders/ThroughFiltersTest.php new file mode 100644 index 00000000..ba980e64 --- /dev/null +++ b/tests/Feature/Integration/Builders/ThroughFiltersTest.php @@ -0,0 +1,162 @@ +create([ + 'custom_field_section_id' => sectionForEntity(Post::class)->getKey(), + 'name' => ucfirst($code), + 'code' => $code, + 'type' => $type, + 'entity_type' => Post::class, + 'settings' => new CustomFieldSettingsData(visible_in_list: true, list_toggleable_hidden: false), + ]); +} + +function commentOn(Post $post): Comment +{ + return Comment::factory()->create(['post_id' => $post->getKey()]); +} + +it('filters by a select field with and without a through path', function (): void { + $field = filterableField('select', 'stage'); + + $won = CustomFieldOption::factory()->create(['custom_field_id' => $field->getKey(), 'name' => 'Won', 'sort_order' => 1]); + $lost = CustomFieldOption::factory()->create(['custom_field_id' => $field->getKey(), 'name' => 'Lost', 'sort_order' => 2]); + + $wonPost = Post::factory()->create(); + $wonPost->saveCustomFieldValue($field, $won->getKey()); + + $lostPost = Post::factory()->create(); + $lostPost->saveCustomFieldValue($field, $lost->getKey()); + + $onWon = commentOn($wonPost); + $onLost = commentOn($lostPost); + + ownTable(Post::class, Post::class) + ->set('tableFilters.custom_fields.stage.values', [$won->getKey()]) + ->assertCanSeeTableRecords([$wonPost]) + ->assertCanNotSeeTableRecords([$lostPost]); + + throughTable(Comment::class, Post::class, 'post') + ->set('tableFilters.custom_fields.stage.values', [$won->getKey()]) + ->assertCanSeeTableRecords([$onWon]) + ->assertCanNotSeeTableRecords([$onLost]); +}); + +it('filters by a tags field with and without a through path', function (): void { + $field = filterableField('tags-input', 'labels'); + + $tagged = Post::factory()->create(); + $tagged->saveCustomFieldValue($field, ['urgent', 'blue']); + + $untagged = Post::factory()->create(); + $untagged->saveCustomFieldValue($field, ['calm']); + + $onTagged = commentOn($tagged); + $onUntagged = commentOn($untagged); + + ownTable(Post::class, Post::class) + ->set('tableFilters.custom_fields.labels.values', ['urgent']) + ->assertCanSeeTableRecords([$tagged]) + ->assertCanNotSeeTableRecords([$untagged]); + + throughTable(Comment::class, Post::class, 'post') + ->set('tableFilters.custom_fields.labels.values', ['urgent']) + ->assertCanSeeTableRecords([$onTagged]) + ->assertCanNotSeeTableRecords([$onUntagged]); +}); + +it('filters by a ternary field with and without a through path', function (): void { + CustomFieldsType::register(['ternary-toggle' => TernaryToggleFieldType::class]); + + $field = filterableField('ternary-toggle', 'archived'); + + $archived = Post::factory()->create(); + $archived->saveCustomFieldValue($field, true); + + $active = Post::factory()->create(); + $active->saveCustomFieldValue($field, false); + + $onArchived = commentOn($archived); + $onActive = commentOn($active); + $orphan = Comment::factory()->create(['post_id' => Post::query()->max('id') + 1000]); + + ownTable(Post::class, Post::class) + ->set('tableFilters.custom_fields.archived.value', true) + ->assertCanSeeTableRecords([$archived]) + ->assertCanNotSeeTableRecords([$active]); + + throughTable(Comment::class, Post::class, 'post') + ->set('tableFilters.custom_fields.archived.value', true) + ->assertCanSeeTableRecords([$onArchived]) + ->assertCanNotSeeTableRecords([$onActive]); + + // A through filter asks about the related record, so a row without one answers neither + // side of the ternary. + throughTable(Comment::class, Post::class, 'post') + ->set('tableFilters.custom_fields.archived.value', false) + ->assertCanSeeTableRecords([$onActive]) + ->assertCanNotSeeTableRecords([$onArchived, $orphan]); +}); + +it('filters by a record field with and without a through path', function (): void { + registerPostLookupEntity(); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'through_filter_related', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Related Post', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + )); + + $code = $definition->fromField->code; + + $linked = Post::factory()->create(['title' => 'Linked Post']); + $linking = Post::factory()->create(['custom_fields' => [$code => [$linked->getKey()]]]); + $unlinked = Post::factory()->create(); + + $onLinking = commentOn($linking); + $onUnlinked = commentOn($unlinked); + + ownTable(Post::class, Post::class) + ->set(sprintf('tableFilters.custom_fields.%s.values', $code), [$linked->getKey()]) + ->assertCanSeeTableRecords([$linking]) + ->assertCanNotSeeTableRecords([$unlinked]); + + throughTable(Comment::class, Post::class, 'post') + ->set(sprintf('tableFilters.custom_fields.%s.values', $code), [$linked->getKey()]) + ->assertCanSeeTableRecords([$onLinking]) + ->assertCanNotSeeTableRecords([$onUnlinked]); +}); + +it('builds a filter for an unsupported relation and rejects it when the query runs', function (): void { + $field = filterableField('select', 'stage'); + + $filter = app(FieldFilterFactory::class)->create($field, 'commentable'); + + expect(fn (): Builder => $filter->apply(Comment::query(), ['values' => [1]])) + ->toThrow(UnsupportedThroughRelationException::class, 'is a MorphTo'); +}); diff --git a/tests/Feature/Integration/Builders/ThroughRelationManagerTest.php b/tests/Feature/Integration/Builders/ThroughRelationManagerTest.php new file mode 100644 index 00000000..5efef764 --- /dev/null +++ b/tests/Feature/Integration/Builders/ThroughRelationManagerTest.php @@ -0,0 +1,90 @@ + $post, + 'pageClass' => EditPost::class, + ]); +} + +it('shows the owner record custom fields on rows that hold none', function (): void { + $field = throughTextField(Post::class, 'category', 'Category'); + + $post = Post::factory()->create(); + $post->saveCustomFieldValue($field, 'Technology'); + + $comments = Comment::factory()->count(3)->create(['post_id' => $post->getKey()]); + + $test = commentsRelationManager($post) + ->assertCanSeeTableRecords($comments) + ->assertTableColumnExists('custom_fields.category') + ->assertCanRenderTableColumn('custom_fields.category'); + + $comments->each(function (Comment $comment) use ($test): void { + $test->assertTableColumnStateSet('custom_fields.category', 'Technology', $comment); + }); +}); + +it('renders empty for a field the owner record has no value for', function (): void { + throughTextField(Post::class, 'category', 'Category'); + + $post = Post::factory()->create(); + $comment = Comment::factory()->create(['post_id' => $post->getKey()]); + + commentsRelationManager($post) + ->assertCanSeeTableRecords([$comment]) + ->assertTableColumnStateSet('custom_fields.category', null, $comment) + ->assertTableColumnFormattedStateSet('custom_fields.category', null, $comment); +}); + +it('sorts relation manager rows by the owner record field', function (): void { + $field = throughTextField(Post::class, 'category', 'Category'); + + $post = Post::factory()->create(); + $post->saveCustomFieldValue($field, 'Technology'); + + $comments = Comment::factory()->count(3)->create(['post_id' => $post->getKey()]); + + commentsRelationManager($post) + ->sortTable('custom_fields.category', 'asc') + ->assertCanSeeTableRecords($comments) + ->sortTable('custom_fields.category', 'desc') + ->assertCanSeeTableRecords($comments); +}); + +it('filters relation manager rows by the owner record field', function (): void { + $field = CustomField::factory()->create([ + 'custom_field_section_id' => sectionForEntity(Post::class)->getKey(), + 'name' => 'Stage', + 'code' => 'stage', + 'type' => 'select', + 'entity_type' => Post::class, + 'settings' => new CustomFieldSettingsData(visible_in_list: true, list_toggleable_hidden: false), + ]); + + $won = CustomFieldOption::factory()->create(['custom_field_id' => $field->getKey(), 'name' => 'Won', 'sort_order' => 1]); + $lost = CustomFieldOption::factory()->create(['custom_field_id' => $field->getKey(), 'name' => 'Lost', 'sort_order' => 2]); + + $post = Post::factory()->create(); + $post->saveCustomFieldValue($field, $won->getKey()); + + $comments = Comment::factory()->count(2)->create(['post_id' => $post->getKey()]); + + commentsRelationManager($post) + ->set('tableFilters.custom_fields.stage.values', [$won->getKey()]) + ->assertCanSeeTableRecords($comments) + ->set('tableFilters.custom_fields.stage.values', [$lost->getKey()]) + ->assertCanNotSeeTableRecords($comments); +}); diff --git a/tests/Feature/Integration/Builders/ThroughRelationResolverTest.php b/tests/Feature/Integration/Builders/ThroughRelationResolverTest.php new file mode 100644 index 00000000..af8521b4 --- /dev/null +++ b/tests/Feature/Integration/Builders/ThroughRelationResolverTest.php @@ -0,0 +1,55 @@ +resolve(new $modelClass, $relation); + + expect($resolved)->toBeInstanceOf($expected); +})->with([ + 'belongs to' => [Comment::class, 'post', BelongsTo::class], + 'has one' => [User::class, 'post', HasOne::class], + 'morph one' => [Post::class, 'featuredComment', MorphOne::class], +]); + +it('rejects a relation the row model does not have', function (): void { + expect(fn () => app(ThroughRelationResolver::class)->resolve(new Comment, 'publisher')) + ->toThrow(UnsupportedThroughRelationException::class, 'has no relation named `publisher`'); +}); + +it('rejects a method that is not a relation', function (): void { + expect(fn () => app(ThroughRelationResolver::class)->resolve(new Comment, 'getTable')) + ->toThrow(UnsupportedThroughRelationException::class, 'has no relation named `getTable`'); +}); + +it('rejects a to-many relation', function (string $modelClass, string $relation, string $type): void { + expect(fn () => app(ThroughRelationResolver::class)->resolve(new $modelClass, $relation)) + ->toThrow(UnsupportedThroughRelationException::class, sprintf('is a %s', $type)); +})->with([ + 'has many' => [User::class, 'posts', 'HasMany'], + 'belongs to many' => [Post::class, 'tagModels', 'BelongsToMany'], +]); + +it('rejects a polymorphic to-one relation', function (): void { + expect(fn () => app(ThroughRelationResolver::class)->resolve(new Comment, 'commentable')) + ->toThrow(UnsupportedThroughRelationException::class, 'is a MorphTo'); +}); + +it('rejects a relation whose target has no custom fields', function (): void { + expect(fn () => app(ThroughRelationResolver::class)->resolve(new Post, 'author')) + ->toThrow(UnsupportedThroughRelationException::class, 'does not implement HasCustomFields'); +}); + +it('names the model and the relation in every rejection', function (): void { + expect(fn () => app(ThroughRelationResolver::class)->resolve(new Post, 'author')) + ->toThrow(UnsupportedThroughRelationException::class, '`author` on `'.Post::class.'`'); +}); diff --git a/tests/Feature/Integration/FieldComponentFactoryBackCompatTest.php b/tests/Feature/Integration/FieldComponentFactoryBackCompatTest.php index e629c003..58723a3b 100644 --- a/tests/Feature/Integration/FieldComponentFactoryBackCompatTest.php +++ b/tests/Feature/Integration/FieldComponentFactoryBackCompatTest.php @@ -4,6 +4,7 @@ use Filament\Forms\Components\Field; use Filament\Forms\Components\TextInput; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use Relaticle\CustomFields\Contracts\FormComponentInterface; use Relaticle\CustomFields\Facades\CustomFieldsType; @@ -24,7 +25,7 @@ // Inline bare-interface implementation — does NOT extend AbstractFormComponent. class BareInterfaceFormComponent implements FormComponentInterface { - public function make(CustomField $customField, array $dependentFieldCodes = [], ?Collection $allFields = null): Field + public function make(CustomField $customField, array $dependentFieldCodes = [], ?Collection $allFields = null, ?Model $record = null): Field { return TextInput::make($customField->getFieldName()); } diff --git a/tests/Feature/Integration/Resources/Pages/CreateRecordTest.php b/tests/Feature/Integration/Resources/Pages/CreateRecordTest.php index 9285061a..4694ade0 100644 --- a/tests/Feature/Integration/Resources/Pages/CreateRecordTest.php +++ b/tests/Feature/Integration/Resources/Pages/CreateRecordTest.php @@ -61,11 +61,12 @@ $this->assertDatabaseHas(Post::class, [ 'author_id' => $newData->author->getKey(), 'content' => $newData->content, - 'tags' => json_encode($newData->tags), 'title' => $newData->title, 'rating' => $newData->rating, ]); + expect(Post::query()->where('title', $newData->title)->value('tags'))->toBe($newData->tags); + $this->assertDatabaseCount('posts', 1); }); @@ -113,19 +114,21 @@ $this->assertDatabaseHas(Post::class, [ 'author_id' => $newData->author->getKey(), 'content' => $newData->content, - 'tags' => json_encode($newData->tags), 'title' => $newData->title, 'rating' => $newData->rating, ]); + expect(Post::query()->where('title', $newData->title)->value('tags'))->toBe($newData->tags); + $this->assertDatabaseHas(Post::class, [ 'author_id' => $newData2->author->getKey(), 'content' => $newData2->content, - 'tags' => json_encode($newData2->tags), 'title' => $newData2->title, 'rating' => $newData2->rating, ]); + expect(Post::query()->where('title', $newData2->title)->value('tags'))->toBe($newData2->tags); + $this->assertDatabaseCount('posts', 2); }); }); @@ -209,12 +212,13 @@ $this->assertDatabaseHas(Post::class, [ 'author_id' => $newData->author->getKey(), 'content' => $newData->content, - 'tags' => json_encode($newData->tags), 'title' => $newData->title, 'rating' => $newData->rating, ]); $post = Post::query()->firstWhere('title', $newData->title); + expect($post->tags)->toBe($newData->tags); + $customFieldValues = $post->customFieldValues->keyBy('customField.code'); expect($customFieldValues)->toHaveCount(2) diff --git a/tests/Feature/Integration/Resources/Pages/EditRecordTest.php b/tests/Feature/Integration/Resources/Pages/EditRecordTest.php index 329fc588..40f787f2 100644 --- a/tests/Feature/Integration/Resources/Pages/EditRecordTest.php +++ b/tests/Feature/Integration/Resources/Pages/EditRecordTest.php @@ -276,6 +276,31 @@ ->and($customFieldValues->get('new_field')?->getValue())->toBe('New Field Value'); }); + it('clears a multi-choice value when the form leaves it empty', function (): void { + $customField = CustomField::factory() + ->ofType('multi-select') + ->withOptions(['Discovery', 'Closed Won']) + ->create([ + 'custom_field_section_id' => $this->section->id, + 'code' => 'stages', + 'entity_type' => Post::class, + ]); + + $optionId = $customField->refresh()->options->first()->getKey(); + + $this->post->saveCustomFieldValue($customField, [$optionId]); + + livewire(EditPost::class, ['record' => $this->post->getKey()]) + ->set('data.custom_fields.stages', []) + ->call('save') + ->assertHasNoFormErrors(); + + $storedValue = $this->post->refresh()->customFieldValues->firstWhere('custom_field_id', $customField->getKey()); + + expect($this->post->getCustomFieldValue($customField))->toBe([]) + ->and($storedValue?->getAttribute($customField->getValueColumn())?->toArray())->toBe([]); + }); + it('validates required custom fields during update', function (): void { // Arrange $requiredCustomField = CustomField::factory()->create([ diff --git a/tests/Feature/Integration/Resources/Pages/ListRecordsTest.php b/tests/Feature/Integration/Resources/Pages/ListRecordsTest.php index b6c42c94..7ed18833 100644 --- a/tests/Feature/Integration/Resources/Pages/ListRecordsTest.php +++ b/tests/Feature/Integration/Resources/Pages/ListRecordsTest.php @@ -2,14 +2,23 @@ declare(strict_types=1); +use Illuminate\Database\Eloquent\Collection as EloquentCollection; +use Illuminate\Database\Eloquent\Factories\Sequence; +use Illuminate\Support\Facades\Exceptions; use Relaticle\CustomFields\Data\CustomFieldSettingsData; +use Relaticle\CustomFields\Data\FieldSlotData; +use Relaticle\CustomFields\Data\RelationshipDefinitionData; use Relaticle\CustomFields\Data\VisibilityConditionData; use Relaticle\CustomFields\Data\VisibilityData; +use Relaticle\CustomFields\Enums\CustomFieldsFeature; +use Relaticle\CustomFields\Enums\RelationshipCardinality; use Relaticle\CustomFields\Enums\VisibilityLogic; use Relaticle\CustomFields\Enums\VisibilityMode; use Relaticle\CustomFields\Enums\VisibilityOperator; +use Relaticle\CustomFields\Exceptions\RelationshipDefinitionDoesNotExistException; use Relaticle\CustomFields\Models\CustomField; use Relaticle\CustomFields\Models\CustomFieldSection; +use Relaticle\CustomFields\Services\Relationships\CreateRelationshipDefinition; use Relaticle\CustomFields\Tests\Fixtures\Models\Post; use Relaticle\CustomFields\Tests\Fixtures\Models\User; use Relaticle\CustomFields\Tests\Fixtures\Resources\Posts\Pages\ListPosts; @@ -21,6 +30,19 @@ $this->actingAs($this->user); }); +/** @return EloquentCollection */ +function orderedPosts(): EloquentCollection +{ + return Post::factory() + ->count(10) + ->sequence(fn (Sequence $sequence): array => [ + 'title' => sprintf('Title %02d', $sequence->index + 1), + 'is_published' => $sequence->index % 2 === 0, + 'author_id' => User::factory()->create(['name' => sprintf('Author %02d', $sequence->index + 1)]), + ]) + ->create(); +} + describe('Page Rendering and Authorization', function (): void { it('can render the list page', function (): void { $this->get(PostResource::getUrl('index')) @@ -86,7 +108,7 @@ describe('Table Sorting', function (): void { beforeEach(function (): void { - $this->posts = Post::factory()->count(10)->create(); + $this->posts = orderedPosts(); }); it('can sort records by standard columns', function (string $column, string $direction): void { @@ -107,7 +129,7 @@ describe('Table Search', function (): void { beforeEach(function (): void { - $this->posts = Post::factory()->count(10)->create(); + $this->posts = orderedPosts(); }); it('can search records by title', function (): void { @@ -152,7 +174,7 @@ describe('Table Filtering', function (): void { beforeEach(function (): void { - $this->posts = Post::factory()->count(10)->create(); + $this->posts = orderedPosts(); }); it('can filter records by is_published status', function (): void { @@ -421,33 +443,81 @@ ]); }); - it('filters by a record field regardless of cardinality', function (bool $allowMultiple, string $code): void { - $field = CustomField::factory()->create([ - 'custom_field_section_id' => $this->section->id, - 'name' => 'Related Post', - 'code' => $code, - 'type' => 'record', - 'entity_type' => Post::class, - 'lookup_type' => Post::class, - 'settings' => new CustomFieldSettingsData( - visible_in_list: true, - list_toggleable_hidden: false, - allow_multiple: $allowMultiple, - ), - ]); + it('filters by a record field regardless of cardinality', function (RelationshipCardinality $cardinality): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_post_'.$cardinality->value, + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: $cardinality, + fromField: new FieldSlotData(name: 'Related Post', sectionId: $this->section->getKey()), + )); + + $code = $definition->fromField->code; $target = Post::factory()->create(); - $linked = Post::factory()->create(); + $linked = Post::factory()->create(['custom_fields' => [$code => [$target->getKey()]]]); $unlinked = Post::factory()->create(); - $linked->saveCustomFieldValue($field, [$target->getKey()]); - livewire(ListPosts::class) ->set(sprintf('tableFilters.custom_fields.%s.values', $code), [$target->getKey()]) ->assertCanSeeTableRecords([$linked]) ->assertCanNotSeeTableRecords([$unlinked]); })->with([ - 'single-value' => [false, 'related_post_single'], - 'multi-value' => [true, 'related_post_multi'], + 'single-value' => RelationshipCardinality::ManyToOne, + 'multi-value' => RelationshipCardinality::ManyToMany, ]); + +}); + +describe('Record Fields Without a Definition', function (): void { + beforeEach(function (): void { + $this->section = CustomFieldSection::factory()->create([ + 'name' => 'Post Table Fields', + 'entity_type' => Post::class, + 'active' => true, + ]); + }); + + it('lists records through a defined field while the relationships feature is off', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_post', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Related Post', sectionId: $this->section->getKey()), + )); + + $target = Post::factory()->create(['title' => 'Linked Target']); + $holder = Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$target->getKey()]]]); + + config('custom-fields.features')->disable(CustomFieldsFeature::SYSTEM_RELATIONSHIPS); + + livewire(ListPosts::class) + ->assertSuccessful() + ->assertCanSeeTableRecords([$holder]) + ->assertSee('Linked Target'); + }); + + it('lists records with the column skipped when a record field has no definition', function (): void { + Exceptions::fake(); + + CustomField::factory()->create([ + 'custom_field_section_id' => $this->section->getKey(), + 'name' => 'Orphaned Record', + 'code' => 'orphaned_record', + 'type' => 'record', + 'entity_type' => Post::class, + 'settings' => new CustomFieldSettingsData(visible_in_list: true, list_toggleable_hidden: false), + ]); + + $post = Post::factory()->create(); + + livewire(ListPosts::class) + ->assertSuccessful() + ->assertCanSeeTableRecords([$post]) + ->assertDontSee('Orphaned Record'); + + Exceptions::assertReported(RelationshipDefinitionDoesNotExistException::class); + Exceptions::assertReportedCount(1); + }); }); diff --git a/tests/Feature/Integration/Resources/Pages/ViewRecordTest.php b/tests/Feature/Integration/Resources/Pages/ViewRecordTest.php index f9734fba..cff4c40b 100644 --- a/tests/Feature/Integration/Resources/Pages/ViewRecordTest.php +++ b/tests/Feature/Integration/Resources/Pages/ViewRecordTest.php @@ -11,8 +11,10 @@ use Relaticle\CustomFields\Enums\VisibilityOperator; use Relaticle\CustomFields\Models\CustomField; use Relaticle\CustomFields\Models\CustomFieldSection; +use Relaticle\CustomFields\Tests\Fixtures\Models\Comment; use Relaticle\CustomFields\Tests\Fixtures\Models\Post; use Relaticle\CustomFields\Tests\Fixtures\Models\User; +use Relaticle\CustomFields\Tests\Fixtures\Resources\Comments\Pages\ViewComment; use Relaticle\CustomFields\Tests\Fixtures\Resources\Posts\Pages\ViewPost; use Relaticle\CustomFields\Tests\Fixtures\Resources\Posts\PostResource; use Spatie\LaravelData\DataCollection; @@ -73,141 +75,97 @@ describe('Conditional Visibility in Infolists', function (): void { beforeEach(function (): void { - // Create custom field section for Posts $this->section = CustomFieldSection::factory()->create([ - 'name' => 'Post Infolist Fields', - 'entity_type' => Post::class, + 'name' => 'Comment Infolist Fields', + 'entity_type' => Comment::class, 'active' => true, 'sort_order' => 1, ]); - }); - it('shows custom field entries when show_when condition is met', function (): void { - // Arrange - Create a base field and a conditional field - $baseField = CustomField::factory()->create([ + $this->statusField = CustomField::factory()->create([ 'custom_field_section_id' => $this->section->id, 'name' => 'Status', 'code' => 'status', 'type' => 'text', - 'entity_type' => Post::class, + 'entity_type' => Comment::class, 'settings' => new CustomFieldSettingsData( visible_in_view: true, ), ]); - $conditionalField = CustomField::factory()->create([ - 'custom_field_section_id' => $this->section->id, - 'name' => 'Priority', - 'code' => 'priority', - 'type' => 'text', - 'entity_type' => Post::class, - 'settings' => new CustomFieldSettingsData( - visible_in_view: true, - visibility: new VisibilityData( - mode: VisibilityMode::SHOW_WHEN, - logic: VisibilityLogic::ALL, - conditions: new DataCollection(VisibilityConditionData::class, [ - new VisibilityConditionData( - field_code: 'status', - operator: VisibilityOperator::EQUALS, - value: 'published' - ), - ]) - ) - ), - ]); + $this->conditionalField = function (string $name, string $code, VisibilityMode $mode): CustomField { + return CustomField::factory()->create([ + 'custom_field_section_id' => $this->section->id, + 'name' => $name, + 'code' => $code, + 'type' => 'text', + 'entity_type' => Comment::class, + 'settings' => new CustomFieldSettingsData( + visible_in_view: true, + visibility: new VisibilityData( + mode: $mode, + logic: VisibilityLogic::ALL, + conditions: new DataCollection(VisibilityConditionData::class, [ + new VisibilityConditionData( + field_code: 'status', + operator: VisibilityOperator::EQUALS, + value: 'published' + ), + ]) + ) + ), + ]); + }; + }); + + it('shows custom field entries when show_when condition is met', function (): void { + $conditionalField = ($this->conditionalField)('Priority', 'priority', VisibilityMode::SHOW_WHEN); - $publishedPost = Post::factory()->create(); - $publishedPost->saveCustomFieldValue($baseField, 'published'); - $publishedPost->saveCustomFieldValue($conditionalField, 'high'); + $published = Comment::factory()->create(); + $published->saveCustomFieldValue($this->statusField, 'published'); + $published->saveCustomFieldValue($conditionalField, 'high'); - $draftPost = Post::factory()->create(); - $draftPost->saveCustomFieldValue($baseField, 'draft'); + $draft = Comment::factory()->create(); + $draft->saveCustomFieldValue($this->statusField, 'draft'); + $draft->saveCustomFieldValue($conditionalField, 'high'); - // Act & Assert - Published post should show both fields - livewire(ViewPost::class, [ - 'record' => $publishedPost->getKey(), + livewire(ViewComment::class, [ + 'record' => $published->getKey(), ]) ->assertSchemaComponentExists('custom_fields.status') ->assertSchemaComponentExists('custom_fields.priority') - ->assertSchemaStateSet([ - 'custom_fields.status' => 'published', - 'custom_fields.priority' => 'high', - ]); + ->assertSee('high'); - // Draft post should only show base field, not conditional field - livewire(ViewPost::class, [ - 'record' => $draftPost->getKey(), + livewire(ViewComment::class, [ + 'record' => $draft->getKey(), ]) ->assertSchemaComponentExists('custom_fields.status') - ->assertSchemaComponentDoesNotExist('custom_fields.priority') - ->assertSchemaStateSet([ - 'custom_fields.status' => 'draft', - ]); - })->todo(); + ->assertSchemaComponentDoesNotExist('custom_fields.priority'); + }); it('hides custom field entries when hide_when condition is met', function (): void { - // Arrange - Create a base field and a conditional field - $baseField = CustomField::factory()->create([ - 'custom_field_section_id' => $this->section->id, - 'name' => 'Status', - 'code' => 'status', - 'type' => 'text', - 'entity_type' => Post::class, - 'settings' => new CustomFieldSettingsData( - visible_in_view: true, - ), - ]); - - $conditionalField = CustomField::factory()->create([ - 'custom_field_section_id' => $this->section->id, - 'name' => 'Internal Notes', - 'code' => 'internal_notes', - 'type' => 'textarea', - 'entity_type' => Post::class, - 'settings' => new CustomFieldSettingsData( - visible_in_view: true, - visibility: new VisibilityData( - mode: VisibilityMode::HIDE_WHEN, - logic: VisibilityLogic::ALL, - conditions: new DataCollection(VisibilityConditionData::class, [ - new VisibilityConditionData( - field_code: 'status', - operator: VisibilityOperator::EQUALS, - value: 'published' - ), - ]) - ) - ), - ]); + $conditionalField = ($this->conditionalField)('Internal Notes', 'internal_notes', VisibilityMode::HIDE_WHEN); - $publishedPost = Post::factory()->create(); - $publishedPost->saveCustomFieldValue($baseField, 'published'); - // Don't save internal notes for published post - it should be hidden anyway + $published = Comment::factory()->create(); + $published->saveCustomFieldValue($this->statusField, 'published'); + $published->saveCustomFieldValue($conditionalField, 'Internal review needed'); - $draftPost = Post::factory()->create(); - $draftPost->saveCustomFieldValue($baseField, 'draft'); - $draftPost->saveCustomFieldValue($conditionalField, 'Internal review needed'); + $draft = Comment::factory()->create(); + $draft->saveCustomFieldValue($this->statusField, 'draft'); + $draft->saveCustomFieldValue($conditionalField, 'Internal review needed'); - // Act & Assert - Published post should hide conditional field - livewire(ViewPost::class, [ - 'record' => $publishedPost->getKey(), + livewire(ViewComment::class, [ + 'record' => $published->getKey(), ]) ->assertSchemaComponentExists('custom_fields.status') ->assertSchemaComponentDoesNotExist('custom_fields.internal_notes') - ->assertSchemaStateSet([ - 'custom_fields.status' => 'published', - ]); + ->assertDontSee('Internal review needed'); - // Draft post should show both fields - livewire(ViewPost::class, [ - 'record' => $draftPost->getKey(), + livewire(ViewComment::class, [ + 'record' => $draft->getKey(), ]) ->assertSchemaComponentExists('custom_fields.status') ->assertSchemaComponentExists('custom_fields.internal_notes') - ->assertSchemaStateSet([ - 'custom_fields.status' => 'draft', - 'custom_fields.internal_notes' => 'Internal review needed', - ]); - })->todo(); + ->assertSee('Internal review needed'); + }); }); diff --git a/tests/Feature/Integration/Resources/ResourceTest.php b/tests/Feature/Integration/Resources/ResourceTest.php index e0f3421b..d0afd518 100644 --- a/tests/Feature/Integration/Resources/ResourceTest.php +++ b/tests/Feature/Integration/Resources/ResourceTest.php @@ -1,5 +1,7 @@ toBe(['Discovery', 'Negotiation', 'Closed Won']) + ->and($parsed['duplicates'])->toBe(0) + ->and($parsed['truncated'])->toBeFalse(); + }); + + it('keeps a name once however the paste cased it', function (): void { + $parsed = OptionNameParser::parse("Discovery\ndiscovery\nDISCOVERY"); + + expect($parsed['names'])->toBe(['Discovery']) + ->and($parsed['duplicates'])->toBe(2); + }); + + it('skips a name the editor already holds', function (): void { + $parsed = OptionNameParser::parse("closed won\nNegotiation", ['Closed Won', '', null]); + + expect($parsed['names'])->toBe(['Negotiation']) + ->and($parsed['duplicates'])->toBe(1); + }); + + it('reads no more names than the cap and says it stopped', function (): void { + $lines = implode("\n", array_map( + fn (int $index): string => 'Option '.$index, + range(1, OptionNameParser::MAX_NAMES + 5), + )); + + $parsed = OptionNameParser::parse($lines); + + expect($parsed['names'])->toHaveCount(OptionNameParser::MAX_NAMES) + ->and($parsed['names'][0])->toBe('Option 1') + ->and($parsed['truncated'])->toBeTrue(); + }); + + it('reads an empty paste as nothing to add', function (): void { + $parsed = OptionNameParser::parse(null); + + expect($parsed['names'])->toBe([]) + ->and($parsed['duplicates'])->toBe(0) + ->and($parsed['truncated'])->toBeFalse(); + }); +}); + +/** + * @return array> + */ +function pasteOptionsInto(CustomField $field, string $names): array +{ + return livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->callAction( + TestAction::make('pasteOptions')->schemaComponent('options'), + ['names' => $names], + ) + ->assertHasNoActionErrors() + ->get('mountedActions.0.data.options'); +} + +describe('the options editor', function (): void { + it('appends the pasted names as keyed rows', function (): void { + $field = CustomField::factory()->ofType('select')->withOptions(['Discovery'])->create(); + + $options = pasteOptionsInto($field, "Negotiation\nClosed Won"); + + expect(array_column($options, 'name'))->toBe(['Discovery', 'Negotiation', 'Closed Won']); + + foreach (array_slice(array_keys($options), 1) as $key) { + expect($key)->toMatch('/^[0-9a-f-]{36}$/'); + } + }); + + it('leaves out a name the editor already holds', function (): void { + $field = CustomField::factory()->ofType('select')->withOptions(['Discovery'])->create(); + + $options = livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->callAction( + TestAction::make('pasteOptions')->schemaComponent('options'), + ['names' => "discovery\nNegotiation"], + ) + ->assertHasNoActionErrors() + ->assertNotified( + Notification::make() + ->success() + ->title('Paste a list of options') + ->body('1 added, 1 skipped as duplicates') + ) + ->get('mountedActions.0.data.options'); + + expect(array_column($options, 'name'))->toBe(['Discovery', 'Negotiation']); + }); + + it('drops the blank row the editor opens on', function (): void { + $field = CustomField::factory()->ofType('select')->create(); + + $page = livewire(ManageCustomField::class, ['field' => $field])->mountAction('edit'); + + $blankKey = (string) Str::uuid(); + $page->set('mountedActions.0.data.options', [$blankKey => ['name' => null]]); + + $options = $page + ->callAction(TestAction::make('pasteOptions')->schemaComponent('options'), ['names' => 'Discovery']) + ->assertHasNoActionErrors() + ->get('mountedActions.0.data.options'); + + expect(array_column($options, 'name'))->toBe(['Discovery']) + ->and($options)->not->toHaveKey($blankKey); + }); + + it('asks for something to paste', function (): void { + $field = CustomField::factory()->ofType('select')->withOptions(['Discovery'])->create(); + + livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->callAction(TestAction::make('pasteOptions')->schemaComponent('options'), ['names' => '']) + ->assertHasActionErrors(['names' => 'required']); + }); + + it('stores the pasted rows through the repeater, in the order they landed', function (): void { + $field = CustomField::factory()->ofType('select')->withOptions(['Discovery'])->create(); + + livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->callAction( + TestAction::make('pasteOptions')->schemaComponent('options'), + ['names' => "Negotiation\nClosed Won"], + ) + ->callMountedAction() + ->assertHasNoActionErrors(); + + $options = $field->refresh()->options()->orderBy('sort_order')->get(); + + expect($options->pluck('name')->all())->toBe(['Discovery', 'Negotiation', 'Closed Won']) + ->and($options->pluck('sort_order')->all())->toBe([1, 2, 3]) + ->and($options->pluck('settings.category')->filter()->all())->toBe([]); + }); + + // The repeater hides its label, and a hint action rides in the label row, so the two + // flavors are asserted on the rendered editor rather than on the schema alone. + it('draws the paste action beside the hidden label in both flavors', function (string $flavor): void { + config()->set('custom-fields.ui.flavor', $flavor); + view()->share('errors', new ViewErrorBag); + + $field = CustomField::factory()->ofType('select')->withOptions(['Discovery'])->create(); + + $component = livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->instance(); + + /** @var Repeater $repeater */ + $repeater = $component->{$component->getMountedActionSchemaName()} + ->getFlatComponents(withHidden: true)['options']; + + expect((string) $repeater->toHtml()) + ->toContain('Paste a list') + ->toContain('Add Option'); + })->with(['polished', 'native']); +}); + +describe('the create form', function (): void { + it('stores the pasted rows on a field that does not exist yet, in the order they landed', function (): void { + pasteIntoNewStageField("Discovery\nNegotiation\nClosed Won"); + + $options = stageOptions(); + + expect($options->pluck('name')->all())->toBe(['Discovery', 'Negotiation', 'Closed Won']) + ->and($options->pluck('sort_order')->all())->toBe([0, 1, 2]); + }); + + it('stamps the tenant on every pasted row', function (): void { + useTenantSchema(7); + + pasteIntoNewStageField("Discovery\nClosed Won"); + + expect(stageOptions()->pluck('tenant_id')->all())->toBe([7, 7]); + })->skip( + fn (): bool => DB::connection()->getDriverName() === 'mysql', + 'MySQL commits DDL implicitly, so the added tenant columns would outlive the test transaction.', + ); +}); + +function pasteIntoNewStageField(string $names): void +{ + livewire(ManageCustomFieldSection::class, [ + 'section' => sectionForEntity(User::class), + 'entityType' => User::class, + ]) + ->mountAction('createField') + ->set('mountedActions.0.data.type', 'select') + ->callAction(TestAction::make('pasteOptions')->schemaComponent('options'), ['names' => $names]) + ->assertHasNoActionErrors() + ->set('mountedActions.0.data.name', 'Stage') + ->set('mountedActions.0.data.code', 'stage') + ->callMountedAction() + ->assertHasNoActionErrors(); +} + +/** + * @return Collection + */ +function stageOptions(): Collection +{ + return CustomField::query() + ->withoutGlobalScopes() + ->where('code', 'stage') + ->firstOrFail() + ->options() + ->withoutGlobalScopes() + ->orderBy('sort_order') + ->get(); +} diff --git a/tests/Feature/OptionCategoryTest.php b/tests/Feature/OptionCategoryTest.php new file mode 100644 index 00000000..5b07dca1 --- /dev/null +++ b/tests/Feature/OptionCategoryTest.php @@ -0,0 +1,504 @@ + array_map( + fn (OptionCategory $category): array => [$category], + OptionCategory::cases(), +)); + +function statusFieldForCategories(): CustomField +{ + return CustomField::factory()->ofType(StatusFieldType::KEY)->create(); +} + +it('treats completed and cancelled as terminal categories', function (): void { + expect(OptionCategory::Unstarted->isTerminal())->toBeFalse() + ->and(OptionCategory::Started->isTerminal())->toBeFalse() + ->and(OptionCategory::Completed->isTerminal())->toBeTrue() + ->and(OptionCategory::Cancelled->isTerminal())->toBeTrue(); +}); + +it('round-trips a category through the stored option settings', function (OptionCategory $category): void { + $option = statusFieldForCategories()->options()->create([ + 'name' => 'Some option', + 'sort_order' => 0, + 'settings' => ['category' => $category->value], + ]); + + expect($option->fresh()->settings->category)->toBe($category); +})->with('option categories'); + +it('stores the category as its backed value in the settings json', function (): void { + $option = statusFieldForCategories()->options()->create([ + 'name' => 'Closed Won', + 'sort_order' => 0, + 'settings' => new CustomFieldOptionSettingsData(category: OptionCategory::Completed), + ]); + + $stored = json_decode($option->fresh()->getRawOriginal('settings'), true, flags: JSON_THROW_ON_ERROR); + + expect($stored['category'])->toBe('completed'); +}); + +it('keeps the category null when an option is saved without one', function (): void { + $option = statusFieldForCategories()->options()->create([ + 'name' => 'Untagged', + 'sort_order' => 0, + ]); + + expect($option->fresh()->settings->category)->toBeNull(); +}); + +it('clears the category back to null', function (): void { + $option = statusFieldForCategories()->options()->create([ + 'name' => 'Done', + 'sort_order' => 0, + 'settings' => ['category' => OptionCategory::Completed->value], + ]); + + $option->update(['settings' => ['category' => null]]); + + expect($option->fresh()->settings->category)->toBeNull(); +}); + +it('fails validation on an unknown category', function (): void { + expect(fn (): CustomFieldOptionSettingsData => CustomFieldOptionSettingsData::validateAndCreate([ + 'category' => 'archived', + ]))->toThrow(ValidationException::class); +}); + +it('refuses to store an unknown category', function (): void { + $field = statusFieldForCategories(); + + expect(fn () => $field->options()->create([ + 'name' => 'Archived', + 'sort_order' => 0, + 'settings' => ['category' => 'archived'], + ]))->toThrow(CannotCastEnum::class); + + expect(CustomFields::newOptionModel()->query()->count())->toBe(0); +}); + +it('returns the options of one category in sort order', function (): void { + $field = statusFieldForCategories(); + $field->options()->createMany([ + ['name' => 'Won Back', 'sort_order' => 3, 'settings' => ['category' => 'completed']], + ['name' => 'Closed Won', 'sort_order' => 2, 'settings' => ['category' => 'completed']], + ['name' => 'Closed Lost', 'sort_order' => 4, 'settings' => ['category' => 'cancelled']], + ['name' => 'Discovery', 'sort_order' => 1], + ]); + + expect($field->optionsInCategory(OptionCategory::Completed)->pluck('name')->all()) + ->toBe(['Closed Won', 'Won Back']) + ->and($field->optionsInCategory(OptionCategory::Cancelled)->pluck('name')->all()) + ->toBe(['Closed Lost']) + ->and($field->optionsInCategory(OptionCategory::Started))->toBeEmpty() + ->and($field->optionsInCategory(OptionCategory::Unstarted))->toBeEmpty(); +}); + +it("keeps another field's options out of the category result", function (): void { + $field = statusFieldForCategories(); + $field->options()->create(['name' => 'Closed Won', 'sort_order' => 1, 'settings' => ['category' => 'completed']]); + + $otherField = statusFieldForCategories(); + $otherField->options()->create(['name' => 'Done', 'sort_order' => 1, 'settings' => ['category' => 'completed']]); + + expect($field->optionsInCategory(OptionCategory::Completed)->pluck('name')->all())->toBe(['Closed Won']); +}); + +it('filters options by category through the query builder', function (): void { + $field = statusFieldForCategories(); + $field->options()->createMany([ + ['name' => 'Closed Won', 'sort_order' => 1, 'settings' => ['category' => 'completed']], + ['name' => 'Won Back', 'sort_order' => 2, 'settings' => ['category' => 'completed']], + ['name' => 'Closed Lost', 'sort_order' => 3, 'settings' => ['category' => 'cancelled']], + ['name' => 'Discovery', 'sort_order' => 4], + ]); + + $completed = CustomFields::newOptionModel()->query()->whereCategory(OptionCategory::Completed)->get(); + + expect($completed->pluck('name')->all())->toEqualCanonicalizing(['Closed Won', 'Won Back']) + ->and(CustomFields::newOptionModel()->query()->whereCategory(OptionCategory::Cancelled)->count())->toBe(1) + ->and(CustomFields::newOptionModel()->query()->whereCategory(OptionCategory::Unstarted)->count())->toBe(0); +}); + +function configureOptionFeatures(bool $colors = false): void +{ + $configurator = FeatureConfigurator::configure()->enable( + CustomFieldsFeature::FIELD_CONDITIONAL_VISIBILITY, + CustomFieldsFeature::UI_TABLE_COLUMNS, + CustomFieldsFeature::UI_TABLE_FILTERS, + CustomFieldsFeature::SYSTEM_MANAGEMENT_INTERFACE, + CustomFieldsFeature::SYSTEM_SECTIONS, + ); + + if ($colors) { + $configurator = $configurator->enable(CustomFieldsFeature::FIELD_OPTION_COLORS); + } + + config(['custom-fields.features' => $configurator]); +} + +function renameFirstOption(CustomField $field, string $name): void +{ + $page = livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->assertActionMounted('edit'); + + $component = $page->instance(); + $itemKey = array_key_first($component->{$component->getMountedActionSchemaName()}->getRawState()['options']); + + $page->set('mountedActions.0.data.options.'.$itemKey.'.name', $name) + ->callMountedAction() + ->assertHasNoActionErrors(); +} + +/** + * @return array{repeater: Repeater, categorySelects: list} + */ +function mountedOptionsRepeater(CustomField $field): array +{ + $page = livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->assertActionMounted('edit'); + + $component = $page->instance(); + $schema = $component->{$component->getMountedActionSchemaName()}; + + /** @var Repeater $repeater */ + $repeater = $schema->getFlatComponents(withHidden: true)['options']; + + $categorySelects = collect($schema->getFlatComponents()) + ->keys() + ->filter(fn (string $key): bool => str_ends_with($key, 'settings.category')) + ->values() + ->all(); + + return ['repeater' => $repeater, 'categorySelects' => $categorySelects]; +} + +it('offers a category column on a status field', function (): void { + $field = CustomField::factory()->ofType(StatusFieldType::KEY)->withOptions(['Discovery', 'Closed Won'])->create(); + + $mounted = mountedOptionsRepeater($field); + + expect($mounted['repeater']->getTableColumns())->toHaveCount(3) + ->and($mounted['categorySelects'])->toHaveCount(2); +}); + +it('offers no category column on a multi-choice field', function (): void { + $field = CustomField::factory()->ofType('multi-select')->withOptions(['Discovery', 'Closed Won'])->create(); + + $mounted = mountedOptionsRepeater($field); + + expect($mounted['repeater']->getTableColumns())->toHaveCount(2) + ->and($mounted['categorySelects'])->toBeEmpty(); +}); + +it('offers no category column on a select field', function (): void { + $field = CustomField::factory()->ofType('select')->withOptions(['Discovery', 'Closed Won'])->create(); + + $mounted = mountedOptionsRepeater($field); + + expect($mounted['repeater']->getTableColumns())->toHaveCount(2) + ->and($mounted['categorySelects'])->toBeEmpty(); +}); + +it('saves a category chosen in the field editor', function (): void { + $section = CustomFieldSection::factory()->forEntityType(User::class)->create(); + + livewire(ManageCustomFieldSection::class, [ + 'section' => $section, + 'entityType' => User::class, + ]) + ->callAction('createField', [ + 'name' => 'Stage', + 'code' => 'stage', + 'type' => StatusFieldType::KEY, + 'entity_type' => User::class, + 'options' => [ + ['name' => 'Discovery', 'settings' => ['category' => 'started']], + ['name' => 'Closed Won', 'settings' => ['category' => 'completed']], + ], + ]) + ->assertHasNoActionErrors(); + + $field = CustomField::query()->withoutGlobalScopes()->where('code', 'stage')->firstOrFail(); + + expect($field->options->pluck('settings.category')->all()) + ->toBe([OptionCategory::Started, OptionCategory::Completed]); +}); + +it('clears a category back to none in the field editor', function (): void { + $field = CustomField::factory()->ofType(StatusFieldType::KEY)->create(); + $option = $field->options()->create([ + 'name' => 'Closed Won', + 'sort_order' => 1, + 'settings' => ['category' => 'completed'], + ]); + + $page = livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->assertActionMounted('edit'); + + $component = $page->instance(); + $itemKey = array_key_first($component->{$component->getMountedActionSchemaName()}->getRawState()['options']); + + $page->set('mountedActions.0.data.options.'.$itemKey.'.settings.category', null) + ->callMountedAction() + ->assertHasNoActionErrors(); + + expect($option->fresh()->settings->category)->toBeNull(); +}); + +it('seeds categories through the migrator options payload', function (): void { + app(CustomFieldsMigrator::class)->new( + model: User::class, + fieldData: new CustomFieldData( + name: 'Stage', + code: 'stage', + type: StatusFieldType::KEY, + ), + )->options([ + 'Discovery', + ['name' => 'Closed Won', 'category' => OptionCategory::Completed, 'color' => '#16a34a'], + ['name' => 'Closed Lost', 'category' => 'cancelled'], + ])->create(); + + $field = CustomField::query()->withoutGlobalScopes()->where('code', 'stage')->firstOrFail(); + + expect($field->options->pluck('name')->all())->toBe(['Discovery', 'Closed Won', 'Closed Lost']) + ->and($field->options->pluck('settings.category')->all()) + ->toBe([null, OptionCategory::Completed, OptionCategory::Cancelled]) + ->and($field->options->pluck('settings.color')->all())->toBe([null, '#16a34a', null]) + ->and($field->optionsInCategory(OptionCategory::Completed)->pluck('name')->all())->toBe(['Closed Won']); +}); + +it('rejects a migrator option array without a name', function (): void { + $migrator = app(CustomFieldsMigrator::class)->new( + model: User::class, + fieldData: new CustomFieldData( + name: 'Stage', + code: 'stage', + type: 'select', + ), + )->options([['category' => 'completed']]); + + expect(fn (): CustomField => $migrator->create())->toThrow(InvalidArgumentException::class); + + expect(CustomFields::newOptionModel()->query()->count())->toBe(0); +}); + +it('keeps a stored category when a select option is renamed', function (): void { + configureOptionFeatures(colors: true); + + $field = CustomField::factory()->ofType('select')->withOptions(['Closed Won'])->create(); + $option = $field->options()->first(); + $option->update(['settings' => ['color' => '#16a34a', 'category' => 'completed']]); + + renameFirstOption($field->fresh(), 'Won'); + + expect($option->fresh()->name)->toBe('Won') + ->and($option->fresh()->settings->category)->toBe(OptionCategory::Completed); +}); + +it('keeps a stored color when option colors are hidden and the option is renamed', function (): void { + configureOptionFeatures(colors: true); + + $field = CustomField::factory()->ofType(StatusFieldType::KEY)->withOptions(['Closed Won'])->create(); + $option = $field->options()->first(); + $option->update(['settings' => ['color' => '#16a34a', 'category' => 'completed']]); + + configureOptionFeatures(); + + renameFirstOption($field->fresh(), 'Won'); + + expect($option->fresh()->settings->color)->toBe('#16a34a') + ->and($option->fresh()->settings->category)->toBe(OptionCategory::Completed); +}); + +it('keeps stored option settings when a multi-choice option is renamed', function (): void { + configureOptionFeatures(colors: true); + + $field = CustomField::factory()->ofType('multi-select')->withOptions(['Closed Won'])->create(); + $option = $field->options()->first(); + $option->update(['settings' => ['color' => '#16a34a', 'category' => 'completed']]); + + renameFirstOption($field->fresh(), 'Won'); + + expect($option->fresh()->settings->color)->toBe('#16a34a') + ->and($option->fresh()->settings->category)->toBe(OptionCategory::Completed); +}); + +it('shows the option colors toggle for a multi-select field', function (): void { + configureOptionFeatures(colors: true); + + $field = CustomField::factory()->ofType('multi-select')->withOptions(['Closed Won'])->create(); + + livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit', ['record' => $field->getKey()]) + ->assertActionMounted('edit') + ->assertSchemaComponentVisible('settings.enable_option_colors'); +}); + +it('rejects a migrator category on a field whose options are not states', function (string $type): void { + $migrator = app(CustomFieldsMigrator::class)->new( + model: User::class, + fieldData: new CustomFieldData( + name: 'Tags', + code: 'tags', + type: $type, + ), + )->options([['name' => 'Closed Won', 'category' => OptionCategory::Completed]]); + + expect(fn (): CustomField => $migrator->create()) + ->toThrow(InvalidArgumentException::class, 'the options of [tags] are not workflow states'); + + expect(CustomFields::newOptionModel()->query()->count())->toBe(0); +})->with(['select', 'multi-select']); + +it('rejects an unknown key in a migrator option array', function (): void { + $migrator = app(CustomFieldsMigrator::class)->new( + model: User::class, + fieldData: new CustomFieldData( + name: 'Stage', + code: 'stage', + type: 'select', + ), + )->options([['name' => 'Closed Won', 'categorie' => 'completed']]); + + expect(fn (): CustomField => $migrator->create())->toThrow(InvalidArgumentException::class); + + expect(CustomFields::newOptionModel()->query()->count())->toBe(0); +}); + +it('reads a category from the loaded options relation without querying again', function (): void { + $field = statusFieldForCategories(); + $field->options()->createMany([ + ['name' => 'Discovery', 'sort_order' => 1], + ['name' => 'Closed Won', 'sort_order' => 2, 'settings' => ['category' => 'completed']], + ]); + + $loaded = CustomField::query()->withoutGlobalScopes()->with('options')->findOrFail($field->getKey()); + + DB::enableQueryLog(); + $completed = $loaded->optionsInCategory(OptionCategory::Completed); + DB::disableQueryLog(); + + expect($completed->pluck('name')->all())->toBe(['Closed Won']) + ->and($completed->modelKeys())->toHaveCount(1) + ->and(DB::getQueryLog())->toBeEmpty(); +}); + +it('applies categories when the migrator updates an existing field', function (): void { + app(CustomFieldsMigrator::class)->new( + model: User::class, + fieldData: new CustomFieldData( + name: 'Stage', + code: 'stage', + type: StatusFieldType::KEY, + section: new CustomFieldSectionData(name: 'Pipeline', code: 'pipeline'), + ), + )->options(['Discovery', 'Closed Won'])->create(); + + app(CustomFieldsMigrator::class) + ->find(User::class, 'stage') + ->options([ + 'Discovery', + ['name' => 'Closed Won', 'category' => OptionCategory::Completed], + ]) + ->update(['name' => 'Stage']); + + $field = CustomField::query()->withoutGlobalScopes()->where('code', 'stage')->firstOrFail(); + + expect($field->options->pluck('settings.category')->all())->toBe([null, OptionCategory::Completed]); +}); + +it('keeps the options, their ids and a stored value when a select becomes a status field', function (): void { + app(CustomFieldsMigrator::class)->new( + model: Post::class, + fieldData: new CustomFieldData( + name: 'Stage', + code: 'stage', + type: 'select', + section: new CustomFieldSectionData(name: 'Pipeline', code: 'pipeline'), + ), + )->options(['Discovery', 'Closed Won'])->create(); + + $field = CustomField::query()->withoutGlobalScopes()->where('code', 'stage')->firstOrFail(); + $optionIds = $field->options()->orderBy('sort_order')->pluck('id')->all(); + $closedWon = $field->options()->where('name', 'Closed Won')->sole(); + + $post = Post::factory()->create(); + $post->saveCustomFieldValue($field, (string) $closedWon->getKey()); + + app(CustomFieldsMigrator::class)->find(Post::class, 'stage')->update(['type' => StatusFieldType::KEY]); + $closedWon->update(['settings' => ['category' => OptionCategory::Completed->value]]); + + $converted = CustomField::query()->withoutGlobalScopes()->where('code', 'stage')->firstOrFail(); + + expect($converted->type)->toBe(StatusFieldType::KEY) + ->and($converted->options()->orderBy('sort_order')->pluck('id')->all())->toEqual($optionIds) + ->and($post->fresh()->getCustomFieldValue($converted))->toEqual($closedWon->getKey()) + ->and($converted->optionsInCategory(OptionCategory::Completed)->pluck('name')->all())->toBe(['Closed Won']) + ->and(mountedOptionsRepeater($converted)['categorySelects'])->toHaveCount(2); +}); + +it('lets a system-defined select become a status field through the migrator recipe', function (): void { + app(CustomFieldsMigrator::class)->new( + model: Post::class, + fieldData: new CustomFieldData( + name: 'Stage', + code: 'stage', + type: 'select', + section: new CustomFieldSectionData(name: 'Pipeline', code: 'pipeline'), + systemDefined: true, + ), + )->options(['Discovery', 'Closed Won'])->create(); + + $field = CustomField::query()->withoutGlobalScopes()->where('code', 'stage')->firstOrFail(); + $optionIds = $field->options()->orderBy('sort_order')->pluck('id')->all(); + $closedWon = $field->options()->where('name', 'Closed Won')->sole(); + + $post = Post::factory()->create(); + $post->saveCustomFieldValue($field, (string) $closedWon->getKey()); + + app(CustomFieldsMigrator::class)->find(Post::class, 'stage')->update(['type' => StatusFieldType::KEY]); + + $converted = CustomField::query()->withoutGlobalScopes()->where('code', 'stage')->firstOrFail(); + + expect($converted->type)->toBe(StatusFieldType::KEY) + ->and($converted->system_defined)->toBeTrue() + ->and($converted->options()->orderBy('sort_order')->pluck('id')->all())->toEqual($optionIds) + ->and($post->fresh()->getCustomFieldValue($converted))->toEqual($closedWon->getKey()); +}); + +it('still refuses to turn a system-defined select into a text field', function (): void { + $field = CustomField::factory()->ofType('select')->systemDefined()->withOptions(['Discovery'])->create(); + + expect(fn () => $field->update(['type' => 'text'])) + ->toThrow(RuntimeException::class, 'Cannot modify name, code, or type of system-defined fields.'); + + expect($field->fresh()->type)->toBe('select'); +}); diff --git a/tests/Feature/Relationships/CardinalityTest.php b/tests/Feature/Relationships/CardinalityTest.php new file mode 100644 index 00000000..f84dd63a --- /dev/null +++ b/tests/Feature/Relationships/CardinalityTest.php @@ -0,0 +1,25 @@ +fromSideIsSingle())->toBeTrue() + ->and(RelationshipCardinality::OneToOne->toSideIsSingle())->toBeTrue() + ->and(RelationshipCardinality::OneToMany->fromSideIsSingle())->toBeFalse() + ->and(RelationshipCardinality::OneToMany->toSideIsSingle())->toBeTrue() + ->and(RelationshipCardinality::ManyToOne->fromSideIsSingle())->toBeTrue() + ->and(RelationshipCardinality::ManyToOne->toSideIsSingle())->toBeFalse() + ->and(RelationshipCardinality::ManyToMany->fromSideIsSingle())->toBeFalse() + ->and(RelationshipCardinality::ManyToMany->toSideIsSingle())->toBeFalse(); +}); + +it('registers the new table names in config', function (): void { + expect(config('custom-fields.database.table_names.custom_field_relationships')) + ->toBe('custom_field_relationships') + ->and(config('custom-fields.database.table_names.custom_field_links')) + ->toBe('custom_field_links') + ->and(config('custom-fields.database.key_type')) + ->toBe('bigint'); +}); diff --git a/tests/Feature/Relationships/CardinalityValidationTest.php b/tests/Feature/Relationships/CardinalityValidationTest.php new file mode 100644 index 00000000..b65f7bdb --- /dev/null +++ b/tests/Feature/Relationships/CardinalityValidationTest.php @@ -0,0 +1,308 @@ +getMorphClass()); + + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'cardinality_ownership', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: $cardinality, + isSymmetric: $symmetric, + fromField: new FieldSlotData(name: 'Owned Post', sectionId: $section->getKey()), + toField: $symmetric ? null : new FieldSlotData(name: 'Owning Post', sectionId: $section->getKey()), + )); +} + +function cardinalityAuthorship(): CustomFieldRelationship +{ + registerPostLookupEntity(); + + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'cardinality_authorship', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Author', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + toField: new FieldSlotData(name: 'Posts', sectionId: sectionForEntity((new User)->getMorphClass())->getKey()), + )); +} + +/** + * @return array + */ +function cardinalityErrors(Closure $write): array +{ + try { + $write(); + } catch (ValidationException $validationException) { + return array_map(strval(...), Arr::flatten($validationException->errors())); + } + + return []; +} + +it('rejects two ids on a single from side', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::ManyToOne); + [$first, $second] = Post::factory()->count(2)->create(); + + $errors = cardinalityErrors(fn (): Post => Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [$first->getKey(), $second->getKey()]], + ])); + + expect($errors)->toBe(['This relationship holds a single record.']) + ->and(CustomFieldLink::query()->count())->toBe(0); +}); + +it('rejects two ids on a single to side', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::OneToMany); + [$first, $second] = Post::factory()->count(2)->create(); + + $errors = cardinalityErrors(fn (): Post => Post::factory()->create([ + 'custom_fields' => [$definition->toField->code => [$first->getKey(), $second->getKey()]], + ])); + + expect($errors)->toBe(['This relationship holds a single record.']) + ->and(CustomFieldLink::query()->count())->toBe(0); +}); + +it('accepts two ids on a many side of the same definition', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::OneToMany); + [$first, $second] = Post::factory()->count(2)->create(); + + $post = Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [$first->getKey(), $second->getKey()]], + ]); + + expect($post->getCustomFieldValue($definition->fromField))->toBe([$first->getKey(), $second->getKey()]); +}); + +it('accepts two ids on both sides of a many to many relationship', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::ManyToMany); + [$first, $second] = Post::factory()->count(2)->create(); + + $from = Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$first->getKey(), $second->getKey()]]]); + $to = Post::factory()->create(['custom_fields' => [$definition->toField->code => [$first->getKey(), $second->getKey()]]]); + + expect($from->getCustomFieldValue($definition->fromField))->toBe([$first->getKey(), $second->getKey()]) + ->and($to->getCustomFieldValue($definition->toField))->toBe([$first->getKey(), $second->getKey()]); +}); + +it('names the record holding a taken single end when writing from the from side', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::OneToOne); + $code = $definition->fromField->code; + + $target = Post::factory()->create(['title' => 'Taken Target']); + Post::factory()->create(['title' => 'First Owner', 'custom_fields' => [$code => [$target->getKey()]]]); + + $errors = cardinalityErrors(fn (): Post => Post::factory()->create([ + 'title' => 'Second Owner', + 'custom_fields' => [$code => [$target->getKey()]], + ])); + + expect($errors)->toBe(['Taken Target is already linked to First Owner. Confirm the replacement to move it.']) + ->and(CustomFieldLink::query()->active()->count())->toBe(1); +}); + +it('names the record holding a taken single end when writing from the to side', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::ManyToOne); + + $target = Post::factory()->create(['title' => 'Single Holder']); + Post::factory()->create(['title' => 'First Owned', 'custom_fields' => [$definition->toField->code => [$target->getKey()]]]); + + $errors = cardinalityErrors(fn (): Post => Post::factory()->create([ + 'title' => 'Second Owned', + 'custom_fields' => [$definition->toField->code => [$target->getKey()]], + ])); + + expect($errors)->toBe(['Single Holder is already linked to First Owned. Confirm the replacement to move it.']) + ->and(CustomFieldLink::query()->active()->count())->toBe(1); +}); + +it('lets a many end hold the same record twice over', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::ManyToMany); + $code = $definition->fromField->code; + + $target = Post::factory()->create(); + Post::factory()->create(['custom_fields' => [$code => [$target->getKey()]]]); + + $second = Post::factory()->create(['custom_fields' => [$code => [$target->getKey()]]]); + + expect($second->getCustomFieldValue($definition->fromField))->toBe([$target->getKey()]) + ->and(CustomFieldLink::query()->active()->count())->toBe(2); +}); + +it('replaces the holder when the payload confirms it', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::OneToOne); + $code = $definition->fromField->code; + + $target = Post::factory()->create(['title' => 'Taken Target']); + $first = Post::factory()->create(['title' => 'First Owner', 'custom_fields' => [$code => [$target->getKey()]]]); + + $second = Post::factory()->create([ + 'title' => 'Second Owner', + 'custom_fields' => [$code => ['ids' => [$target->getKey()], 'replace' => true]], + ]); + + expect($second->getCustomFieldValue($definition->fromField))->toBe([$target->getKey()]) + ->and($first->getCustomFieldValue($definition->fromField))->toBe([]) + ->and(CustomFieldLink::query()->active()->count())->toBe(1) + ->and(CustomFieldLink::query()->whereNotNull('active_until')->count())->toBe(1); +}); + +it('lets a record replace its own single link without confirmation', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::ManyToOne); + $code = $definition->fromField->code; + + [$first, $second] = Post::factory()->count(2)->create(); + $post = Post::factory()->create(['custom_fields' => [$code => [$first->getKey()]]]); + + $post->update(['custom_fields' => [$code => [$second->getKey()]]]); + + expect($post->getCustomFieldValue($definition->fromField))->toBe([$second->getKey()]); +}); + +it('rejects two ids on a symmetric single relationship', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::OneToOne, symmetric: true); + [$first, $second] = Post::factory()->count(2)->create(); + + $errors = cardinalityErrors(fn (): Post => Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [$first->getKey(), $second->getKey()]], + ])); + + expect($errors)->toBe(['This relationship holds a single record.']); +}); + +it('names the holder of a taken symmetric end', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::OneToOne, symmetric: true); + $code = $definition->fromField->code; + + $target = Post::factory()->create(['title' => 'Taken Partner']); + Post::factory()->create(['title' => 'First Partner', 'custom_fields' => [$code => [$target->getKey()]]]); + + $errors = cardinalityErrors(fn (): Post => Post::factory()->create([ + 'title' => 'Second Partner', + 'custom_fields' => [$code => [$target->getKey()]], + ])); + + expect($errors)->toBe(['Taken Partner is already linked to First Partner. Confirm the replacement to move it.']); +}); + +it('drops the rejected payload instead of retrying it on the next save', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::ManyToOne); + $code = $definition->fromField->code; + + [$first, $second] = Post::factory()->count(2)->create(); + $post = Post::factory()->create(['title' => 'Rejected']); + + $errors = cardinalityErrors(fn (): bool => $post->update([ + 'custom_fields' => [$code => [$first->getKey(), $second->getKey()]], + ])); + + $post->update(['title' => 'Retried']); + + expect($errors)->toBe(['This relationship holds a single record.']) + ->and($post->fresh()->title)->toBe('Retried') + ->and(CustomFieldLink::query()->count())->toBe(0); +}); + +it('names the holder by end, not by an id two entity types share', function (): void { + $definition = cardinalityAuthorship(); + + [$holder, $writer] = User::factory()->count(2)->create(); + + $posts = Post::factory()->count((int) $holder->getKey() + 1)->create(); + $taken = $posts->first(); + $decoy = $posts->firstWhere('id', $holder->getKey()); + + $taken->update(['title' => 'Taken Post']); + $decoy->update(['title' => 'Decoy Post']); + + app(LinkWriter::class)->apply($taken, $definition->fromField, [$holder->getKey()]); + + $errors = cardinalityErrors(fn (): mixed => app(LinkWriter::class) + ->apply($writer, $definition->toField, [$decoy->getKey(), $taken->getKey()])); + + expect($errors)->toBe([sprintf('Taken Post is already linked to %s. Confirm the replacement to move it.', $holder->getKey())]); +}); + +it('reports the single-record message through the panel form', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::ManyToOne); + $code = $definition->fromField->code; + + [$first, $second] = Post::factory()->count(2)->create(); + $post = Post::factory()->create(); + + livewire(EditPost::class, ['record' => $post->getRouteKey()]) + ->fillForm([ + 'title' => $post->title, + 'author_id' => $post->author_id, + 'rating' => $post->rating, + 'custom_fields' => [$code => [$first->getKey(), $second->getKey()]], + ]) + ->call('save') + ->assertHasFormErrors(['custom_fields.'.$code]); + + expect(CustomFieldLink::query()->count())->toBe(0); +}); + +it('reports a single-side overflow once through the panel form', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::ManyToOne); + $code = $definition->fromField->code; + + [$first, $second] = Post::factory()->count(2)->create(); + $post = Post::factory()->create(); + + $form = livewire(EditPost::class, ['record' => $post->getRouteKey()]) + ->fillForm([ + 'title' => $post->title, + 'author_id' => $post->author_id, + 'rating' => $post->rating, + 'custom_fields' => [$code => [$first->getKey(), $second->getKey()]], + ]) + ->call('save'); + + expect($form->instance()->getErrorBag()->get('data.custom_fields.'.$code)) + ->toBe(['This relationship holds a single record.']); +}); + +it('reports the holder message through the panel form', function (): void { + $definition = cardinalityPairing(RelationshipCardinality::OneToOne); + $code = $definition->fromField->code; + + $target = Post::factory()->create(['title' => 'Taken Target']); + Post::factory()->create(['title' => 'First Owner', 'custom_fields' => [$code => [$target->getKey()]]]); + $post = Post::factory()->create(['title' => 'Second Owner']); + + livewire(EditPost::class, ['record' => $post->getRouteKey()]) + ->fillForm([ + 'title' => $post->title, + 'author_id' => $post->author_id, + 'rating' => $post->rating, + 'custom_fields' => [$code => [$target->getKey()]], + ]) + ->call('save') + ->assertHasFormErrors([ + 'custom_fields.'.$code => 'Taken Target is already linked to First Owner. Confirm the replacement to move it.', + ]); + + expect(CustomFieldLink::query()->active()->count())->toBe(1); +}); diff --git a/tests/Feature/Relationships/CreateRelationshipDefinitionTest.php b/tests/Feature/Relationships/CreateRelationshipDefinitionTest.php new file mode 100644 index 00000000..2c212808 --- /dev/null +++ b/tests/Feature/Relationships/CreateRelationshipDefinitionTest.php @@ -0,0 +1,433 @@ +execute(new RelationshipDefinitionData( + code: 'authorship', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Author', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + toField: new FieldSlotData(name: 'Posts', sectionId: sectionForEntity((new User)->getMorphClass())->getKey()), + )); +} + +it('creates a paired definition with two record fields in one transaction', function (): void { + $definition = authorship(); + + expect($definition->cardinality)->toBe(RelationshipCardinality::ManyToOne) + ->and($definition->fromField)->not->toBeNull() + ->and($definition->fromField->type)->toBe('record') + ->and($definition->fromField->code)->toBe('author') + ->and($definition->fromField->entity_type)->toBe((new Post)->getMorphClass()) + ->and($definition->toField->entity_type)->toBe((new User)->getMorphClass()) + ->and($definition->toField->code)->toBe('posts') + ->and(CustomField::query()->count())->toBe(2); +}); + +it('creates a one-way definition with a single field', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'referrer', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Referred by', sectionId: sectionForEntity((new User)->getMorphClass())->getKey()), + )); + + expect($definition->to_field_id)->toBeNull() + ->and($definition->isHeadless())->toBeFalse() + ->and(CustomField::query()->count())->toBe(1); +}); + +it('creates a headless definition with no fields', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'works_with', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + )); + + expect($definition->isHeadless())->toBeTrue() + ->and(CustomField::query()->count())->toBe(0); +}); + +it('points both slots at one field for a symmetric definition', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'spouse', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::OneToOne, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Spouse', sectionId: sectionForEntity((new User)->getMorphClass())->getKey()), + )); + + expect($definition->from_field_id)->toBe($definition->to_field_id) + ->and($definition->directionFor($definition->fromField))->toBe(CustomFieldRelationship::DIRECTION_FROM) + ->and(CustomField::query()->count())->toBe(1); +}); + +it('rejects a symmetric definition across two entity types', function (): void { + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'spouse', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::OneToOne, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Spouse'), + )); +})->throws(InvalidArgumentException::class); + +it('rejects a second slot on a symmetric definition', function (): void { + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'spouse', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::OneToOne, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Spouse'), + toField: new FieldSlotData(name: 'Spouse of'), + )); +})->throws(InvalidArgumentException::class); + +it('rejects a directional cardinality on a symmetric definition', function (): void { + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'sibling', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::OneToMany, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Sibling'), + )); +})->throws(InvalidArgumentException::class); + +it('writes no field when the definition is rejected', function (): void { + $create = fn (): CustomFieldRelationship => app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'spouse', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::OneToOne, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Spouse'), + )); + + expect($create)->toThrow(InvalidArgumentException::class) + ->and(CustomField::query()->withDeactivated()->count())->toBe(0) + ->and(CustomFieldRelationship::query()->count())->toBe(0); +}); + +it('rejects an end that resolves to no model', function (): void { + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'authorship', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: 'acme_ghosts', + cardinality: RelationshipCardinality::ManyToOne, + )); +})->throws(InvalidArgumentException::class); + +it('rejects a code already used by another definition', function (): void { + authorship(); + + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'authorship', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + )); +})->throws(InvalidArgumentException::class); + +it('unpairs on delete, keeping each field on its own one-way definition', function (): void { + $definition = authorship(); + CustomFieldLink::factory()->create(['relationship_id' => $definition->getKey()]); + + app(DeleteRelationshipDefinition::class)->execute($definition, deleteFields: false); + + expect(CustomFieldRelationship::query()->count())->toBe(2) + ->and(CustomFieldRelationship::query()->whereNotNull('from_field_id')->count())->toBe(1) + ->and(CustomFieldRelationship::query()->whereNotNull('to_field_id')->count())->toBe(1) + ->and(CustomFieldLink::query()->count())->toBe(0) + ->and(CustomField::query()->count())->toBe(2); +}); + +it('deletes the slot fields when asked', function (): void { + $definition = authorship(); + CustomFieldLink::factory()->create(['relationship_id' => $definition->getKey()]); + + app(DeleteRelationshipDefinition::class)->execute($definition, deleteFields: true); + + expect(CustomFieldRelationship::query()->count())->toBe(0) + ->and(CustomFieldLink::query()->count())->toBe(0) + ->and(CustomField::query()->withDeactivated()->count())->toBe(0); +}); + +it('keeps the definition and its edges when one slot field is deleted', function (): void { + $definition = authorship(); + CustomFieldLink::factory()->create(['relationship_id' => $definition->getKey()]); + + $definition->toField->delete(); + + expect($definition->refresh()->to_field_id)->toBeNull() + ->and($definition->from_field_id)->not->toBeNull() + ->and(CustomFieldLink::query()->count())->toBe(1); +}); + +it('still unpairs a deleted slot field while the relationships feature is off', function (): void { + $definition = authorship(); + CustomFieldLink::factory()->create(['relationship_id' => $definition->getKey()]); + + config('custom-fields.features')->disable(CustomFieldsFeature::SYSTEM_RELATIONSHIPS); + + $definition->toField->delete(); + + expect($definition->refresh()->to_field_id)->toBeNull() + ->and($definition->from_field_id)->not->toBeNull() + ->and(CustomFieldLink::query()->count())->toBe(1); +}); + +it('removes a one-way definition and its edges when its only field is deleted', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'referrer', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Referred by', sectionId: sectionForEntity((new User)->getMorphClass())->getKey()), + )); + CustomFieldLink::factory()->create(['relationship_id' => $definition->getKey()]); + + $definition->fromField->delete(); + + expect(CustomFieldRelationship::query()->count())->toBe(0) + ->and(CustomFieldLink::query()->count())->toBe(0); +}); + +it('removes a symmetric definition when its single field is deleted', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'spouse', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::OneToOne, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Spouse', sectionId: sectionForEntity((new User)->getMorphClass())->getKey()), + )); + CustomFieldLink::factory()->create(['relationship_id' => $definition->getKey()]); + + $definition->fromField->delete(); + + expect(CustomFieldRelationship::query()->count())->toBe(0) + ->and(CustomFieldLink::query()->count())->toBe(0); +}); + +it('unpairs a slot from a foreign tenant context', function (): void { + useTenantSchema(7); + + $definition = authorship(); + CustomFieldLink::factory()->create([ + 'relationship_id' => $definition->getKey(), + 'tenant_id' => 7, + ]); + $toField = $definition->toField; + + TenantContextService::setTenantId(8); + + $toField->delete(); + + TenantContextService::setTenantId(7); + + expect($definition->refresh()->to_field_id)->toBeNull() + ->and($definition->from_field_id)->not->toBeNull() + ->and(CustomFieldLink::query()->count())->toBe(1); +})->skip( + fn (): bool => DB::connection()->getDriverName() === 'mysql', + 'MySQL commits DDL implicitly, so the added tenant columns would outlive the test transaction.', +); + +it('refuses to move the ends of an existing definition', function (): void { + $definition = authorship(); + + $definition->update(['to_entity_type' => (new Post)->getMorphClass()]); +})->throws(RuntimeException::class); + +it('allows a cardinality change on an existing definition', function (): void { + $definition = authorship(); + + $definition->update(['cardinality' => RelationshipCardinality::ManyToMany]); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::ManyToMany); +}); + +it('stamps the tenant on the definition and both slot fields', function (): void { + useTenantSchema(7); + + $definition = authorship(); + + expect($definition->tenant_id)->toBe(7) + ->and($definition->fromField->tenant_id)->toBe(7) + ->and($definition->toField->tenant_id)->toBe(7); + + DB::table(config('custom-fields.database.table_names.custom_field_relationships')) + ->where('id', $definition->getKey()) + ->update(['tenant_id' => 8]); + + expect(CustomFieldRelationship::query()->count())->toBe(0); +})->skip( + fn (): bool => DB::connection()->getDriverName() === 'mysql', + 'MySQL commits DDL implicitly, so the added tenant columns would outlive the test transaction.', +); + +describe('adopting a field the caller already wrote', function (): void { + it('wraps an existing record field as the slot instead of creating one', function (): void { + $field = CustomField::factory()->create([ + 'code' => 'mentor', + 'name' => 'Mentor', + 'type' => 'record', + 'entity_type' => (new User)->getMorphClass(), + 'custom_field_section_id' => sectionForEntity((new User)->getMorphClass())->getKey(), + ]); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'mentorship', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Mentor', fieldId: $field->getKey()), + )); + + expect($definition->from_field_id)->toBe($field->getKey()) + ->and(CustomField::query()->count())->toBe(1); + }); + + it('refuses a field that is not a record field', function (): void { + $field = CustomField::factory()->create([ + 'code' => 'stage', + 'type' => 'select', + 'entity_type' => (new User)->getMorphClass(), + 'custom_field_section_id' => sectionForEntity((new User)->getMorphClass())->getKey(), + ]); + + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'mentorship', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Stage', fieldId: $field->getKey()), + )); + })->throws(InvalidArgumentException::class); + + it('refuses a field that sits on the other entity', function (): void { + $field = CustomField::factory()->create([ + 'code' => 'mentor', + 'type' => 'record', + 'entity_type' => (new Post)->getMorphClass(), + 'custom_field_section_id' => sectionForEntity((new Post)->getMorphClass())->getKey(), + ]); + + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'mentorship', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Mentor', fieldId: $field->getKey()), + )); + })->throws(InvalidArgumentException::class); + + it('refuses a field that already renders a relationship', function (): void { + $definition = authorship(); + + app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'second_authorship', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Author', fieldId: $definition->from_field_id), + )); + })->throws(InvalidArgumentException::class); +}); + +describe('preset migrations', function (): void { + it('gives a record field a one-way definition instead of a lookup column', function (): void { + app(CustomFieldsMigrator::class)->new( + model: Post::class, + fieldData: new CustomFieldData( + name: 'Sales Representative', + code: 'sales_rep', + type: 'record', + section: new CustomFieldSectionData(name: 'Sales', code: 'sales'), + ), + )->lookupType(User::class)->create(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect($definition->from_entity_type)->toBe((new Post)->getMorphClass()) + ->and($definition->to_entity_type)->toBe((new User)->getMorphClass()) + ->and($definition->cardinality)->toBe(RelationshipCardinality::ManyToOne) + ->and($definition->fromField->code)->toBe('sales_rep') + ->and($definition->to_field_id)->toBeNull(); + }); + + it('reads allow_multiple once, to pick the cardinality', function (): void { + app(CustomFieldsMigrator::class)->new( + model: Post::class, + fieldData: new CustomFieldData( + name: 'Reviewers', + code: 'reviewers', + type: 'record', + section: new CustomFieldSectionData(name: 'Sales', code: 'sales'), + settings: new CustomFieldSettingsData(allow_multiple: true), + ), + )->lookupType(User::class)->create(); + + expect(CustomFieldRelationship::query()->sole()->cardinality) + ->toBe(RelationshipCardinality::ManyToMany); + }); + + it('takes an explicit cardinality over the settings flag', function (): void { + app(CustomFieldsMigrator::class)->new( + model: Post::class, + fieldData: new CustomFieldData( + name: 'Owner', + code: 'owner', + type: 'record', + section: new CustomFieldSectionData(name: 'Sales', code: 'sales'), + settings: new CustomFieldSettingsData(allow_multiple: true), + ), + )->lookupType(User::class, RelationshipCardinality::OneToOne)->create(); + + expect(CustomFieldRelationship::query()->sole()->cardinality) + ->toBe(RelationshipCardinality::OneToOne); + }); + + it('refuses to move the ends of a field it already created', function (): void { + app(CustomFieldsMigrator::class)->new( + model: Post::class, + fieldData: new CustomFieldData( + name: 'Sales Representative', + code: 'sales_rep', + type: 'record', + section: new CustomFieldSectionData(name: 'Sales', code: 'sales'), + ), + )->lookupType(User::class)->create(); + + app(CustomFieldsMigrator::class) + ->find(Post::class, 'sales_rep') + ->update(['lookup_type' => (new Post)->getMorphClass()]); + })->throws(InvalidArgumentException::class); +}); diff --git a/tests/Feature/Relationships/EntityDeletionTest.php b/tests/Feature/Relationships/EntityDeletionTest.php new file mode 100644 index 00000000..c44bacca --- /dev/null +++ b/tests/Feature/Relationships/EntityDeletionTest.php @@ -0,0 +1,180 @@ +getMorphClass()); + + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'deletion_mentions', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Mentions', sectionId: $section->getKey()), + toField: new FieldSlotData(name: 'Mentioned By', sectionId: $section->getKey()), + )); +} + +function deletionCommentary(): CustomFieldRelationship +{ + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'deletion_commentary', + fromEntityType: (new Comment)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'About', sectionId: sectionForEntity((new Comment)->getMorphClass())->getKey()), + )); +} + +it('sweeps both ends and the history when a record is force deleted', function (): void { + $definition = deletionMentions(); + $code = $definition->fromField->code; + + $subject = Post::factory()->create(); + [$mentioned, $closed, $other] = Post::factory()->count(3)->create(); + + $subject->update(['custom_fields' => [$code => [$closed->getKey()]]]); + $subject->update(['custom_fields' => [$code => [$mentioned->getKey()]]]); + Post::factory()->create(['custom_fields' => [$code => [$subject->getKey()]]]); + $unrelated = Post::factory()->create(['custom_fields' => [$code => [$other->getKey()]]]); + + expect(CustomFieldLink::query()->count())->toBe(4); + + $subject->forceDelete(); + + expect(CustomFieldLink::query()->count())->toBe(1) + ->and(CustomFieldLink::query()->sole()->from_entity_id)->toEqual($unrelated->getKey()); +}); + +it('sweeps the edges of a force-deleted record even while the relationships feature is off', function (): void { + $definition = deletionCommentary(); + $post = Post::factory()->create(); + + $comment = Comment::factory()->create(['custom_fields' => [$definition->fromField->code => [$post->getKey()]]]); + + config('custom-fields.features')->disable(CustomFieldsFeature::SYSTEM_RELATIONSHIPS); + + $comment->forceDelete(); + + expect(CustomFieldLink::query()->count())->toBe(0); +}); + +it('sweeps the edges of a record whose model deletes outright', function (): void { + $definition = deletionCommentary(); + $post = Post::factory()->create(); + + $comment = Comment::factory()->create(['custom_fields' => [$definition->fromField->code => [$post->getKey()]]]); + + expect(CustomFieldLink::query()->count())->toBe(1); + + $comment->delete(); + + expect(CustomFieldLink::query()->count())->toBe(0); +}); + +it('keeps every edge when a record is soft deleted', function (): void { + $definition = deletionMentions(); + $code = $definition->fromField->code; + + $mentioned = Post::factory()->create(); + $post = Post::factory()->create(['custom_fields' => [$code => [$mentioned->getKey()]]]); + + $post->delete(); + + expect(CustomFieldLink::query()->active()->count())->toBe(1) + ->and($post->getCustomFieldValue($definition->fromField))->toBe([$mentioned->getKey()]); +}); + +it('keeps the edges of a soft deleted target and reads them again after a restore', function (): void { + $definition = deletionMentions(); + $code = $definition->fromField->code; + + $mentioned = Post::factory()->create(); + $post = Post::factory()->create(['custom_fields' => [$code => [$mentioned->getKey()]]]); + + $mentioned->delete(); + $mentioned->restore(); + + expect(CustomFieldLink::query()->active()->count())->toBe(1) + ->and($post->getCustomFieldValue($definition->fromField))->toBe([$mentioned->getKey()]); +}); + +it('skips a trashed end in the table column and the infolist entry', function (): void { + $definition = deletionMentions(); + $code = $definition->fromField->code; + + $kept = Post::factory()->create(['title' => 'Kept Target']); + $trashed = Post::factory()->create(['title' => 'Trashed Target']); + $post = Post::factory()->create(['title' => 'Mentioning Host', 'custom_fields' => [$code => [$kept->getKey(), $trashed->getKey()]]]); + + $trashed->delete(); + + livewire(ListPosts::class) + ->assertSee('Kept Target') + ->assertDontSee('Trashed Target'); + + livewire(ViewPost::class, ['record' => $post->getRouteKey()]) + ->assertSee('Kept Target') + ->assertDontSee('Trashed Target'); +}); + +it('skips a trashed end in the record select options', function (): void { + registerPostLookupEntity(); + + $kept = Post::factory()->create(['title' => 'Kept Target']); + $trashed = Post::factory()->create(['title' => 'Trashed Target']); + + $trashed->delete(); + + $ids = [(string) $kept->getKey(), (string) $trashed->getKey()]; + + expect(array_column(recordSelectFor(Post::class)->getRecordsByIds($ids), 'label'))->toBe(['Kept Target']) + ->and(array_column(recordSelectInitialOptions(), 'label'))->not->toContain('Trashed Target'); +}); + +it('keeps a record saveable while one of its targets is trashed', function (): void { + $definition = deletionMentions(); + $code = $definition->fromField->code; + + $kept = Post::factory()->create(); + $trashed = Post::factory()->create(); + $post = Post::factory()->create(['custom_fields' => [$code => [$kept->getKey(), $trashed->getKey()]]]); + + $trashed->delete(); + + $post->update(['custom_fields' => [$code => [$kept->getKey(), $trashed->getKey()]]]); + + expect(CustomFieldLink::query()->active()->count())->toBe(2); +}); + +it('counts an active edge as a value of the relationship slot', function (): void { + $definition = deletionMentions(); + $code = $definition->fromField->code; + + $mentioned = Post::factory()->create(); + + expect($definition->fromField->hasValues())->toBeFalse(); + + $post = Post::factory()->create(['custom_fields' => [$code => [$mentioned->getKey()]]]); + + expect($definition->fromField->hasValues())->toBeTrue(); + + $post->update(['custom_fields' => [$code => []]]); + + expect($definition->fromField->hasValues())->toBeFalse(); +}); diff --git a/tests/Feature/Relationships/LinkActorResolverTest.php b/tests/Feature/Relationships/LinkActorResolverTest.php new file mode 100644 index 00000000..153024b7 --- /dev/null +++ b/tests/Feature/Relationships/LinkActorResolverTest.php @@ -0,0 +1,38 @@ +create(); + $this->actingAs($user); + + expect(app(LinkActorResolverInterface::class))->toBeInstanceOf(AuthenticatedActorResolver::class) + ->and(app(LinkActorResolverInterface::class)->resolve())->toBeSameModel($user); +}); + +it('resolves null when nobody is authenticated', function (): void { + auth()->logout(); + + expect(app(LinkActorResolverInterface::class)->resolve())->toBeNull(); +}); + +it('lets a host swap the resolver for its own actor', function (): void { + $agent = Post::factory()->create(); + + app()->singleton(LinkActorResolverInterface::class, fn (): LinkActorResolverInterface => new class($agent) implements LinkActorResolverInterface + { + public function __construct(private readonly Post $agent) {} + + public function resolve(): Post + { + return $this->agent; + } + }); + + expect(app(LinkActorResolverInterface::class)->resolve())->toBeSameModel($agent); +}); diff --git a/tests/Feature/Relationships/LinkModelTest.php b/tests/Feature/Relationships/LinkModelTest.php new file mode 100644 index 00000000..1d1b7a19 --- /dev/null +++ b/tests/Feature/Relationships/LinkModelTest.php @@ -0,0 +1,119 @@ +create(); + + $link = CustomFieldLink::factory()->create([ + 'relationship_id' => $definition->id, + 'from_entity_type' => 'post', + 'from_entity_id' => 1, + 'to_entity_type' => 'user', + 'to_entity_id' => 2, + ]); + + expect(CustomFieldLink::query()->active()->count())->toBe(1) + ->and($link->source)->toBe(CustomFieldLink::SOURCE_USER) + ->and($link->relationship)->toBeSameModel($definition); + + $link->close(now()); + + expect(CustomFieldLink::query()->active()->count())->toBe(0) + ->and(CustomFieldLink::query()->count())->toBe(1) + ->and($link->refresh()->active_until)->not->toBeNull(); +}); + +it('refuses a duplicate active edge at the database level', function (): void { + $definition = CustomFieldRelationship::factory()->create(); + $attributes = [ + 'relationship_id' => $definition->id, + 'from_entity_type' => 'post', + 'from_entity_id' => 1, + 'to_entity_type' => 'user', + 'to_entity_id' => 2, + ]; + + CustomFieldLink::factory()->create($attributes); + + DB::transaction(function () use ($attributes): void { + CustomFieldLink::factory()->create($attributes); + }); +}) + ->throws(QueryException::class) + ->skip( + fn (): bool => ! in_array(DB::connection()->getDriverName(), ['pgsql', 'sqlite'], true), + 'The MySQL family has no partial index, so the writer is the only wall there.', + ); + +it('allows re-linking after a close', function (): void { + $definition = CustomFieldRelationship::factory()->create(); + $attributes = [ + 'relationship_id' => $definition->id, + 'from_entity_type' => 'post', + 'from_entity_id' => 1, + 'to_entity_type' => 'user', + 'to_entity_id' => 2, + ]; + + CustomFieldLink::factory()->create($attributes)->close(now()); + + expect(CustomFieldLink::factory()->create($attributes))->toBeInstanceOf(CustomFieldLink::class); +}); + +it('keeps a closed edge queryable as history', function (): void { + $definition = CustomFieldRelationship::factory()->create(); + $closedAt = now()->subDay(); + + $link = CustomFieldLink::factory()->create(['relationship_id' => $definition->id]); + $link->close($closedAt); + + expect(CustomFieldLink::query()->whereNotNull('active_until')->count())->toBe(1) + ->and($link->refresh()->active_until?->toDateTimeString())->toBe($closedAt->toDateTimeString()) + ->and($link->active_from)->not->toBeNull(); +}); + +it('eager loads both ends under the names they are read from', function (): void { + $definition = CustomFieldRelationship::factory()->create(); + $from = Post::factory()->create(); + $to = Post::factory()->create(); + + CustomFieldLink::factory()->create([ + 'relationship_id' => $definition->id, + 'from_entity_type' => $from->getMorphClass(), + 'from_entity_id' => $from->getKey(), + 'to_entity_type' => $to->getMorphClass(), + 'to_entity_id' => $to->getKey(), + ]); + + $link = CustomFieldLink::query()->with(['fromEntity', 'toEntity'])->sole(); + + DB::flushQueryLog(); + DB::enableQueryLog(); + + $ends = [$link->fromEntity, $link->toEntity]; + + $queries = DB::getQueryLog(); + DB::disableQueryLog(); + DB::flushQueryLog(); + + expect($link->relationLoaded('fromEntity'))->toBeTrue() + ->and($link->relationLoaded('toEntity'))->toBeTrue() + ->and($ends[0])->toBeSameModel($from) + ->and($ends[1])->toBeSameModel($to) + ->and($queries)->toBeEmpty(); +}); + +it('resolves the link model through the swap registry', function (): void { + expect(CustomFields::linkModel())->toBe(CustomFieldLink::class) + ->and(CustomFields::newLinkModel())->toBeInstanceOf(CustomFieldLink::class) + ->and(CustomFields::newLinkModel()->getTable()) + ->toBe(config('custom-fields.database.table_names.custom_field_links')); +}); diff --git a/tests/Feature/Relationships/LinkWriterTest.php b/tests/Feature/Relationships/LinkWriterTest.php new file mode 100644 index 00000000..15cb049c --- /dev/null +++ b/tests/Feature/Relationships/LinkWriterTest.php @@ -0,0 +1,430 @@ +execute(new RelationshipDefinitionData( + code: 'authorship', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: $cardinality, + fromField: new FieldSlotData(name: 'Author', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + toField: new FieldSlotData(name: 'Posts', sectionId: sectionForEntity((new User)->getMorphClass())->getKey()), + )); +} + +function makeSpouse(): CustomFieldRelationship +{ + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'spouse', + fromEntityType: (new User)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::OneToOne, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Spouse', sectionId: sectionForEntity((new User)->getMorphClass())->getKey()), + )); +} + +it('adds, keeps, and removes links from a diff', function (): void { + $definition = makeAuthorship(RelationshipCardinality::ManyToMany); + $post = Post::factory()->create(); + [$a, $b, $c] = User::factory()->count(3)->create(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$a->getKey(), $b->getKey()]); + + $kept = CustomFieldLink::query()->where('to_entity_id', $b->getKey())->sole(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$b->getKey(), $c->getKey()]); + + expect(CustomFieldLink::query()->active()->orderBy('sort_order')->pluck('to_entity_id')->map(intval(...))->all()) + ->toBe([$b->getKey(), $c->getKey()]) + ->and(CustomFieldLink::query()->count())->toBe(3) + ->and($kept->refresh()->active_until)->toBeNull() + ->and($kept->sort_order)->toBe(0); +}); + +it('replaces the link when a single end takes a new target', function (): void { + $definition = makeAuthorship(); + $post = Post::factory()->create(); + [$a, $b] = User::factory()->count(2)->create(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$a->getKey()]); + app(LinkWriter::class)->apply($post, $definition->fromField, [$b->getKey()]); + + $active = CustomFieldLink::query()->active()->get(); + + expect($active)->toHaveCount(1) + ->and($active->first()->to_entity_id)->toEqual($b->getKey()) + ->and(CustomFieldLink::query()->count())->toBe(2); +}); + +it('clears every link for an empty payload', function (): void { + $definition = makeAuthorship(); + $post = Post::factory()->create(); + $user = User::factory()->create(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + app(LinkWriter::class)->apply($post, $definition->fromField, []); + + expect(CustomFieldLink::query()->active()->count())->toBe(0) + ->and(CustomFieldLink::query()->count())->toBe(1); +}); + +it('writes the same edge whichever side applies it', function (): void { + $definition = makeAuthorship(); + $post = Post::factory()->create(); + $user = User::factory()->create(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + app(LinkWriter::class)->apply($user, $definition->toField, [$post->getKey()]); + + expect(CustomFieldLink::query()->count())->toBe(1) + ->and(CustomFieldLink::query()->active()->count())->toBe(1); +}); + +it('clears from the far side of the same edge', function (): void { + $definition = makeAuthorship(); + $post = Post::factory()->create(); + $user = User::factory()->create(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + app(LinkWriter::class)->apply($user, $definition->toField, []); + + expect(CustomFieldLink::query()->active()->count())->toBe(0); +}); + +it('leaves the many end alone when only the other end is single', function (): void { + $definition = makeAuthorship(); + [$postA, $postB] = Post::factory()->count(2)->create(); + $user = User::factory()->create(); + + app(LinkWriter::class)->apply($postA, $definition->fromField, [$user->getKey()]); + app(LinkWriter::class)->apply($postB, $definition->fromField, [$user->getKey()]); + + expect(CustomFieldLink::query()->active()->count())->toBe(2); +}); + +it('takes a taken one to one end on confirmation and closes the displaced edge', function (): void { + $definition = makeAuthorship(RelationshipCardinality::OneToOne); + [$postA, $postB] = Post::factory()->count(2)->create(); + $user = User::factory()->create(); + + app(LinkWriter::class)->apply($postA, $definition->fromField, [$user->getKey()]); + app(LinkWriter::class)->apply($postB, $definition->fromField, [$user->getKey()], confirmed: [(string) $user->getKey()]); + + $active = CustomFieldLink::query()->active()->get(); + + expect($active)->toHaveCount(1) + ->and($active->first()->from_entity_id)->toEqual($postB->getKey()) + ->and(CustomFieldLink::query()->count())->toBe(2) + ->and(CustomFieldLink::query()->whereNotNull('active_until')->sole()->from_entity_id)->toEqual($postA->getKey()); +}); + +it('canonicalizes a symmetric edge to one row read from both records', function (): void { + $definition = makeSpouse(); + [$a, $b] = User::factory()->count(2)->create(); + + app(LinkWriter::class)->apply($a, $definition->fromField, [$b->getKey()]); + app(LinkWriter::class)->apply($b, $definition->fromField, [$a->getKey()]); + + $link = CustomFieldLink::query()->sole(); + + expect(CustomFieldLink::query()->count())->toBe(1) + ->and(strcmp((string) $link->from_entity_id, (string) $link->to_entity_id))->toBeLessThanOrEqual(0) + ->and([(string) $link->from_entity_id, (string) $link->to_entity_id]) + ->toEqualCanonicalizing([(string) $a->getKey(), (string) $b->getKey()]); +}); + +it('takes a taken symmetric end from either side on confirmation', function (): void { + $definition = makeSpouse(); + [$a, $b, $c] = User::factory()->count(3)->create(); + + app(LinkWriter::class)->apply($a, $definition->fromField, [$b->getKey()]); + app(LinkWriter::class)->apply($c, $definition->fromField, [$b->getKey()], confirmed: [(string) $b->getKey()]); + + expect(CustomFieldLink::query()->active()->count())->toBe(1) + ->and(CustomFieldLink::query()->count())->toBe(2); +}); + +it('emits a created and a closed event carrying the edge', function (): void { + Event::fake([RelationshipLinkCreated::class, RelationshipLinkClosed::class]); + + $definition = makeAuthorship(); + $post = Post::factory()->create(); + $user = User::factory()->create(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + app(LinkWriter::class)->apply($post, $definition->fromField, []); + + Event::assertDispatchedTimes(RelationshipLinkCreated::class, 1); + Event::assertDispatchedTimes(RelationshipLinkClosed::class, 1); + Event::assertDispatched(RelationshipLinkClosed::class, fn (RelationshipLinkClosed $event): bool => $event->link->to_entity_id === $user->getKey() + && $event->link->active_until !== null); +}); + +it('touches nothing when the payload matches the stored links', function (): void { + $definition = makeAuthorship(); + $post = Post::factory()->create(); + $user = User::factory()->create(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + + Event::fake([RelationshipLinkCreated::class, RelationshipLinkClosed::class]); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + + Event::assertNothingDispatched(); + expect(CustomFieldLink::query()->count())->toBe(1); +}); + +it('stamps the actor and the source on the edge', function (): void { + $definition = makeAuthorship(); + $actor = User::factory()->create(); + $this->actingAs($actor); + $post = Post::factory()->create(); + $user = User::factory()->create(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()], CustomFieldLink::SOURCE_IMPORT); + + $link = CustomFieldLink::query()->sole(); + + expect($link->created_by_id)->toEqual($actor->getKey()) + ->and($link->created_by_type)->toBe($actor->getMorphClass()) + ->and($link->source)->toBe(CustomFieldLink::SOURCE_IMPORT) + ->and($link->active_from)->not->toBeNull(); +}); + +it('rejects a target that does not exist', function (): void { + $definition = makeAuthorship(); + $post = Post::factory()->create(); + + $apply = fn (): mixed => app(LinkWriter::class)->apply($post, $definition->fromField, [404]); + + expect($apply)->toThrow(ValidationException::class) + ->and(CustomFieldLink::query()->count())->toBe(0); +}); + +it('rejects a target the host query cannot reach', function (): void { + $definition = makeAuthorship(); + $post = Post::factory()->create(); + $foreign = User::factory()->create(); + + User::addGlobalScope('other_tenant', fn (Builder $query) => $query->whereKeyNot($foreign->getKey())); + + $apply = fn (): mixed => app(LinkWriter::class)->apply($post, $definition->fromField, [$foreign->getKey()]); + + expect($apply)->toThrow(ValidationException::class) + ->and(CustomFieldLink::query()->count())->toBe(0); +}); + +it('refuses a record field with no definition', function (): void { + $field = CustomField::factory()->create([ + 'type' => 'record', + 'entity_type' => (new Post)->getMorphClass(), + 'custom_field_section_id' => sectionForEntity((new Post)->getMorphClass())->getKey(), + ]); + + app(LinkWriter::class)->apply(Post::factory()->create(), $field, []); +})->throws(InvalidArgumentException::class); + +it('refuses a record from the wrong end of the definition', function (): void { + $definition = makeAuthorship(); + + app(LinkWriter::class)->apply(User::factory()->create(), $definition->fromField, []); +})->throws(InvalidArgumentException::class); + +it('translates a lost race into a validation error', function (): void { + $definition = makeAuthorship(RelationshipCardinality::ManyToMany); + $post = Post::factory()->create(); + $user = User::factory()->create(); + + $raced = false; + + CustomFieldLink::creating(function () use (&$raced, $definition, $post, $user): void { + if ($raced) { + return; + } + + $raced = true; + + CustomFieldLink::factory()->create([ + 'relationship_id' => $definition->getKey(), + 'from_entity_type' => $post->getMorphClass(), + 'from_entity_id' => $post->getKey(), + 'to_entity_type' => $user->getMorphClass(), + 'to_entity_id' => $user->getKey(), + ]); + }); + + $apply = fn (): mixed => app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + + expect($apply)->toThrow(ValidationException::class) + ->and(CustomFieldLink::query()->count())->toBe(0); +})->skip( + fn (): bool => DB::connection()->getDriverName() === 'mysql', + 'The MySQL family has no partial index, so there is no constraint to race against.', +); + +it('copies the tenant of the definition onto every edge', function (): void { + useTenantSchema(7); + + $definition = makeAuthorship(); + $post = Post::factory()->create(); + $user = User::factory()->create(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + + expect(CustomFieldLink::query()->sole()->tenant_id)->toBe(7); + + DB::table(config('custom-fields.database.table_names.custom_field_links'))->update(['tenant_id' => 8]); + + expect(CustomFieldLink::query()->count())->toBe(0); +})->skip( + fn (): bool => DB::connection()->getDriverName() === 'mysql', + 'MySQL commits DDL implicitly, so the added tenant columns would outlive the test transaction.', +); + +it('holds its events until the surrounding transaction commits', function (): void { + $definition = makeAuthorship(); + $post = Post::factory()->create(); + $user = User::factory()->create(); + + $heard = []; + Event::listen(RelationshipLinkCreated::class, function () use (&$heard): void { + $heard[] = 'created'; + }); + + try { + DB::transaction(function () use ($definition, $post, $user): void { + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + + throw new RuntimeException('the caller failed after the links were written'); + }); + } catch (RuntimeException) { + // + } + + expect($heard)->toBe([]) + ->and(CustomFieldLink::query()->count())->toBe(0); + + DB::transaction(function () use ($definition, $post, $user): void { + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + }); + + expect($heard)->toBe(['created']) + ->and(CustomFieldLink::query()->active()->count())->toBe(1); +}); + +it('rethrows a unique violation that is not the edge index', function (): void { + $definition = makeAuthorship(); + $post = Post::factory()->create(); + $user = User::factory()->create(); + $section = sectionForEntity('acme_reports'); + + CustomFieldLink::created(function () use ($section): void { + CustomFieldSection::factory()->create([ + 'entity_type' => $section->entity_type, + 'code' => $section->code, + ]); + }); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); +})->throws(UniqueConstraintViolationException::class); + +it('applies one to many from both ends', function (): void { + $definition = makeAuthorship(RelationshipCardinality::OneToMany); + [$postA, $postB] = Post::factory()->count(2)->create(); + [$userA, $userB] = User::factory()->count(2)->create(); + + app(LinkWriter::class)->apply($postA, $definition->fromField, [$userA->getKey(), $userB->getKey()]); + + expect(CustomFieldLink::query()->active()->count())->toBe(2); + + app(LinkWriter::class)->apply($userB, $definition->toField, [$postB->getKey()]); + + $active = CustomFieldLink::query()->active()->get(); + + expect($active)->toHaveCount(2) + ->and(CustomFieldLink::query()->count())->toBe(3) + ->and($active->firstWhere('to_entity_id', $userB->getKey())->from_entity_id)->toEqual($postB->getKey()); +}); + +it('applies many to one from the to end', function (): void { + $definition = makeAuthorship(); + [$postA, $postB] = Post::factory()->count(2)->create(); + [$userA, $userB] = User::factory()->count(2)->create(); + + app(LinkWriter::class)->apply($userA, $definition->toField, [$postA->getKey(), $postB->getKey()]); + + expect(CustomFieldLink::query()->active()->count())->toBe(2); + + app(LinkWriter::class)->apply($userB, $definition->toField, [$postB->getKey()], confirmed: [(string) $postB->getKey()]); + + expect(CustomFieldLink::query()->active()->count())->toBe(2) + ->and(CustomFieldLink::query()->count())->toBe(3) + ->and(CustomFieldLink::query()->active()->where('from_entity_id', $postB->getKey())->sole()->to_entity_id) + ->toEqual($userB->getKey()); +}); + +it('replaces a taken one to one end from the to side and keeps the closed edge', function (): void { + $definition = makeAuthorship(RelationshipCardinality::OneToOne); + $post = Post::factory()->create(); + [$userA, $userB] = User::factory()->count(2)->create(); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$userA->getKey()]); + app(LinkWriter::class)->apply($userB, $definition->toField, [$post->getKey()], confirmed: [(string) $post->getKey()]); + + $closed = CustomFieldLink::query()->whereNotNull('active_until')->sole(); + + expect(CustomFieldLink::query()->active()->sole()->to_entity_id)->toEqual($userB->getKey()) + ->and($closed->to_entity_id)->toEqual($userA->getKey()) + ->and($closed->active_until)->not->toBeNull(); +}); + +it('locks the definition row for every cardinality that constrains an end', function (RelationshipCardinality $cardinality, bool $locks): void { + $definition = makeAuthorship($cardinality); + $post = Post::factory()->create(); + $user = User::factory()->create(); + + $statements = []; + DB::listen(function (QueryExecuted $query) use (&$statements): void { + $statements[] = strtolower($query->sql); + }); + + app(LinkWriter::class)->apply($post, $definition->fromField, [$user->getKey()]); + + $definitions = config('custom-fields.database.table_names.custom_field_relationships'); + $locked = array_filter($statements, fn (string $sql): bool => str_contains($sql, $definitions) + && str_contains($sql, 'for update')); + + expect($locked !== [])->toBe($locks); +})->with([ + 'one to one' => [RelationshipCardinality::OneToOne, true], + 'one to many' => [RelationshipCardinality::OneToMany, true], + 'many to one' => [RelationshipCardinality::ManyToOne, true], + 'many to many' => [RelationshipCardinality::ManyToMany, false], +])->skip( + fn (): bool => DB::connection()->getDriverName() === 'sqlite', + 'SQLite compiles no lock clause, so there is no statement to assert on.', +); diff --git a/tests/Feature/Relationships/RecordChipsAndPickerTest.php b/tests/Feature/Relationships/RecordChipsAndPickerTest.php new file mode 100644 index 00000000..25c233f9 --- /dev/null +++ b/tests/Feature/Relationships/RecordChipsAndPickerTest.php @@ -0,0 +1,666 @@ +set('custom-fields.entity_configuration', + EntityConfigurator::configure() + ->autoDiscover(false) + ->cache(false) + ->models([ + EntityModel::configure( + modelClass: Post::class, + labelSingular: 'Post', + primaryAttribute: 'title', + searchAttributes: ['title'], + resourceClass: PostResource::class, + features: [EntityFeature::CUSTOM_FIELDS, EntityFeature::LOOKUP_SOURCE], + avatarConfiguration: $avatarAttribute === null + ? null + : new AvatarConfiguration(attribute: $avatarAttribute), + ), + ]) + ); + + app()->forgetInstance(EntityManager::class); +} + +function relatedPostsField(RelationshipCardinality $cardinality = RelationshipCardinality::ManyToMany): CustomFieldRelationship +{ + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_posts', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: $cardinality, + fromField: new FieldSlotData( + name: 'Related Posts', + sectionId: sectionForEntity((new Post)->getMorphClass())->getKey(), + type: RelationshipFieldType::KEY, + ), + )); +} + +function chipLinkQueries(callable $work): int +{ + $links = config('custom-fields.database.table_names.custom_field_links'); + + DB::flushQueryLog(); + DB::enableQueryLog(); + + try { + $work(); + + return count(array_filter( + DB::getQueryLog(), + static fn (array $entry): bool => str_contains($entry['query'], $links), + )); + } finally { + DB::disableQueryLog(); + DB::flushQueryLog(); + } +} + +describe('record chips', function (): void { + it('draws a linked record as a chip with its avatar and a page to open', function (): void { + registerChipEntity(avatarAttribute: 'content'); + $definition = relatedPostsField(); + + $target = Post::factory()->create(['title' => 'Aurora Labs', 'content' => 'https://avatars.test/aurora.png']); + Post::factory()->create([ + 'title' => 'Holder', + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + $table = livewire(ListPosts::class) + ->assertSeeHtml('https://avatars.test/aurora.png') + ->assertSee('Aurora Labs'); + + rendersPolished(UiSurface::RecordChips) + ? $table->assertSeeHtml('data-surface="record-chips"')->assertSeeHtml('fi-cf-record-chip') + : $table->assertDontSeeHtml('data-surface="record-chips"')->assertDontSeeHtml('fi-cf-record-chip'); + }); + + it('draws the chips in the order the links were set', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + $first = Post::factory()->create(['title' => 'Aurora Labs']); + $second = Post::factory()->create(['title' => 'Borealis Group']); + $host = Post::factory()->create([ + 'title' => 'Holder', + 'custom_fields' => [$definition->fromField->code => [$second->getKey(), $first->getKey()]], + ]); + + expect($host->fresh()->getCustomFieldValue($definition->fromField)) + ->toBe([$second->getKey(), $first->getKey()]); + + livewire(ListPosts::class)->assertSeeInOrder(['Borealis Group', '1 more']); + }); + + it('says how many chips it is hiding instead of showing a bare count', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + $targets = Post::factory()->count(3)->create(); + Post::factory()->create([ + 'title' => 'Holder', + 'custom_fields' => [$definition->fromField->code => $targets->pluck('id')->all()], + ]); + + $table = livewire(ListPosts::class)->assertSee('2 more'); + + rendersPolished(UiSurface::RecordChips) + ? $table->assertSeeHtml('fi-cf-record-chips-overflow') + : $table->assertDontSeeHtml('fi-cf-record-chips-overflow'); + }); + + it('reads provenance from the edge the page already loaded', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + $target = Post::factory()->create(['title' => 'Aurora Labs']); + $host = Post::factory()->create([ + 'title' => 'Holder', + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + $table = livewire(ListPosts::class); + + // Provenance is a polished affordance: the stock column draws the record and nothing + // about the edge it came from, while the ledger reads the same either way. + rendersPolished(UiSurface::RecordChips) + ? $table->assertSeeHtml('data-provenance')->assertSee('Linked by hand') + : $table->assertDontSeeHtml('data-provenance')->assertSee('Aurora Labs'); + + $host->load('outgoingLinks.createdBy'); + + $withActor = array_values(app(RecordChips::class)->provenance($host, $definition->fromField)); + + expect($withActor)->toHaveCount(1) + ->and($withActor[0])->toContain(auth()->user()->name) + ->and($withActor[0])->toStartWith('Linked by '); + }); + + it('reads a host source the package has no words for as itself', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + $target = Post::factory()->create(['title' => 'Aurora Labs']); + $host = Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + CustomFieldLink::query()->update(['source' => 'webhook', 'created_by_type' => null, 'created_by_id' => null]); + $host->load('outgoingLinks.createdBy'); + + $provenance = array_values(app(RecordChips::class)->provenance($host, $definition->fromField)); + + expect($provenance)->toHaveCount(1) + ->and($provenance[0])->toContain('webhook') + ->and($provenance[0])->not->toContain('custom-fields::'); + }); + + it('says a record is not linked rather than leaving the chip row blank', function (): void { + registerChipEntity(); + relatedPostsField(); + + Post::factory()->create(['title' => 'Holder']); + + $table = livewire(ListPosts::class); + + rendersPolished(UiSurface::RecordChips) + ? $table->assertSee('Not linked') + : $table->assertDontSee('Not linked'); + }); + + it('keeps the stock chip markup in the native flavor', function (): void { + config()->set('custom-fields.ui.flavor', 'native'); + registerChipEntity(); + $definition = relatedPostsField(); + + $target = Post::factory()->create(['title' => 'Aurora Labs']); + $host = Post::factory()->create([ + 'title' => 'Holder', + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + livewire(ListPosts::class) + ->assertDontSeeHtml('data-surface="record-chips"') + ->assertSee('Aurora Labs'); + }); + + it('reads the same number of link queries however many rows the table holds', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + $target = Post::factory()->create(['title' => 'Aurora Labs']); + + Post::factory()->count(2)->create([ + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + // The ledger's one-time table check would otherwise count as a first-render query. + livewire(ListPosts::class)->assertSuccessful(); + + $small = chipLinkQueries(fn (): mixed => livewire(ListPosts::class)->assertSuccessful()); + + Post::factory()->count(6)->create([ + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + $large = chipLinkQueries(fn (): mixed => livewire(ListPosts::class)->assertSuccessful()); + + expect($small)->toBe(2) + ->and($large)->toBe($small); + }); +}); + +describe('record picker', function (): void { + it('renders the polished picker with keyboard support and a create-new link', function (): void { + registerChipEntity(); + relatedPostsField(); + + $page = livewire(EditPost::class, ['record' => Post::factory()->create()->getRouteKey()]) + ->assertSeeHtml('role="listbox"') + ->assertSeeHtml('aria-activedescendant') + ->assertSee('Create a new Post'); + + rendersPolished(UiSurface::RecordPicker) + ? $page->assertSeeHtml('data-surface="record-picker"') + : $page->assertDontSeeHtml('data-surface="record-picker"'); + }); + + it('keeps the stock picker in the native flavor', function (): void { + config()->set('custom-fields.ui.flavor', 'native'); + registerChipEntity(); + relatedPostsField(); + + livewire(EditPost::class, ['record' => Post::factory()->create()->getRouteKey()]) + ->assertDontSeeHtml('data-surface="record-picker"') + ->assertSeeHtml('role="listbox"'); + }); + + it('offers the linked records a keyboard reorder in both flavors', function (string $flavor): void { + config()->set('custom-fields.ui.flavor', $flavor); + registerChipEntity(); + $definition = relatedPostsField(); + + [$first, $second] = Post::factory()->count(2)->create(); + $host = Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [$first->getKey(), $second->getKey()]], + ]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->assertSeeHtml('fi-cf-record-chip-move') + ->assertSeeHtml('moveRecord(record.id, -1)') + ->assertSeeHtml('moveRecord(record.id, 1)') + ->assertSeeHtml('index === selectedRecords.length - 1'); + })->with(['polished', 'native']); + + it('offers the same reorder on a record field holding many records', function (): void { + registerChipEntity(); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'many_posts', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData( + name: 'Many Posts', + sectionId: sectionForEntity((new Post)->getMorphClass())->getKey(), + type: RecordFieldType::KEY, + ), + )); + + $host = Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => Post::factory()->count(2)->create()->pluck('id')->all()], + ]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->assertSeeHtml('fi-cf-record-chip-move'); + }); + + it('offers no create-new when the entity has no resource to create in', function (): void { + registerPostLookupEntity(); + relatedPostsField(); + + livewire(EditPost::class, ['record' => Post::factory()->create()->getRouteKey()]) + ->assertDontSee('Create a new'); + }); + + it('reads the overflow as a sentence, not as a raw plural string, in both flavors', function (string $flavor): void { + config()->set('custom-fields.ui.flavor', $flavor); + registerChipEntity(); + $definition = relatedPostsField(); + + $targets = Post::factory()->count(4)->create(); + $host = Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => $targets->pluck('id')->all()], + ]); + + // The trigger picks its form in the browser, so the server render is asserted on the + // two forms it picks between: the raw pluralized string must never reach the client. + $html = livewire(EditPost::class, ['record' => $host->getRouteKey()])->html(); + + // The labels reach the client as escaped JSON, so the assertion reads the same bytes + // the browser parses. + $quote = '\u0022'; + + expect($html) + ->toContain('overflowLabels') + ->toContain($quote.'one'.$quote.':'.$quote.':count more'.$quote) + ->toContain($quote.'many'.$quote.':'.$quote.':count more'.$quote) + ->toContain($quote.'one'.$quote.':'.$quote.':count record linked'.$quote) + ->toContain($quote.'many'.$quote.':'.$quote.':count records linked'.$quote) + ->not->toContain('{1} :count more|[2,*] :count more') + ->not->toContain('{1} :count record linked|'); + + expect(trans_choice('custom-fields::custom-fields.record.more_records', 3, ['count' => 3]))->toBe('3 more') + ->and(trans_choice('custom-fields::custom-fields.record.announce_count', 3, ['count' => 3]))->toBe('3 records linked'); + })->with(['polished', 'native']); + + it('reads the chip overflow as a sentence for three hidden records', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + $targets = Post::factory()->count(4)->create(); + Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => $targets->pluck('id')->all()], + ]); + + $table = livewire(ListPosts::class) + ->assertSee('3 more') + ->assertDontSee('{1} :count more'); + + rendersPolished(UiSurface::RecordChips) + ? $table->assertSeeHtml('fi-cf-record-chips-overflow') + : $table->assertDontSeeHtml('fi-cf-record-chips-overflow'); + }); + + it('reorders the links to the order the chips were left in', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + [$first, $second] = Post::factory()->count(2)->create(); + $host = Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [$first->getKey(), $second->getKey()]], + ]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.'.$definition->fromField->code, [$second->getKey(), $first->getKey()]) + ->call('save') + ->assertHasNoFormErrors(); + + expect($host->fresh()->getCustomFieldValue($definition->fromField->fresh())) + ->toBe([$second->getKey(), $first->getKey()]); + }); + + it('unlinks the last record the field was holding', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + $target = Post::factory()->create(); + $host = Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.'.$definition->fromField->code, []) + ->call('save') + ->assertHasNoFormErrors(); + + expect($host->fresh()->getCustomFieldValue($definition->fromField->fresh()))->toBe([]) + ->and(CustomFieldLink::query()->active()->count())->toBe(0) + ->and(CustomFieldLink::query()->whereNotNull('active_until')->count())->toBe(1); + }); + + it('leaves the links of a field the conditions hide where they are', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + CustomField::factory()->ofType('text')->create([ + 'custom_field_section_id' => $definition->fromField->custom_field_section_id, + 'entity_type' => Post::class, + 'name' => 'Stage', + 'code' => 'stage', + ]); + + $definition->fromField->update([ + 'settings' => [ + 'visibility' => [ + 'mode' => VisibilityMode::SHOW_WHEN, + 'logic' => VisibilityLogic::ALL, + 'conditions' => [[ + 'field_code' => 'stage', + 'operator' => VisibilityOperator::EQUALS, + 'value' => 'open', + ]], + ], + ], + ]); + + $target = Post::factory()->create(); + $host = Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.stage', 'closed') + ->set('data.custom_fields.'.$definition->fromField->code, []) + ->call('save') + ->assertHasNoFormErrors(); + + expect($host->fresh()->getCustomFieldValue($definition->fromField->fresh())) + ->toBe([$target->getKey()]); + }); + + it('leaves the links of a field whose condition the server cannot reproduce', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + // A model-attribute condition is evaluated against the record, not the form state, so + // the server cannot say what the client is showing. An empty payload must not clear. + $definition->fromField->update([ + 'settings' => [ + 'visibility' => [ + 'mode' => VisibilityMode::SHOW_WHEN, + 'logic' => VisibilityLogic::ALL, + 'conditions' => [[ + 'field_code' => 'is_published', + 'operator' => VisibilityOperator::EQUALS, + 'value' => true, + 'source' => ConditionSource::ModelAttribute, + ]], + ], + ], + ]); + + $target = Post::factory()->create(); + $host = Post::factory()->create([ + 'is_published' => false, + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.'.$definition->fromField->code, []) + ->call('save') + ->assertHasNoFormErrors(); + + expect($host->fresh()->getCustomFieldValue($definition->fromField->fresh())) + ->toBe([$target->getKey()]); + }); + + it('unlinks the last record on a field its condition is showing', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + CustomField::factory()->ofType('text')->create([ + 'custom_field_section_id' => $definition->fromField->custom_field_section_id, + 'entity_type' => Post::class, + 'name' => 'Stage', + 'code' => 'stage', + ]); + + $definition->fromField->update([ + 'settings' => [ + 'visibility' => [ + 'mode' => VisibilityMode::SHOW_WHEN, + 'logic' => VisibilityLogic::ALL, + 'conditions' => [[ + 'field_code' => 'stage', + 'operator' => VisibilityOperator::EQUALS, + 'value' => 'open', + ]], + ], + ], + ]); + + $target = Post::factory()->create(); + $host = Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.stage', 'open') + ->set('data.custom_fields.'.$definition->fromField->code, []) + ->call('save') + ->assertHasNoFormErrors(); + + expect($host->fresh()->getCustomFieldValue($definition->fromField->fresh()))->toBe([]) + ->and(CustomFieldLink::query()->active()->count())->toBe(0); + }); + + it('unlinks a record when its chip is taken off the field', function (): void { + registerChipEntity(); + $definition = relatedPostsField(); + + [$kept, $removed] = Post::factory()->count(2)->create(); + $host = Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [$kept->getKey(), $removed->getKey()]], + ]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.'.$definition->fromField->code, [$kept->getKey()]) + ->call('save') + ->assertHasNoFormErrors(); + + expect($host->fresh()->getCustomFieldValue($definition->fromField->fresh()))->toBe([$kept->getKey()]) + ->and(CustomFieldLink::query()->whereNotNull('active_until')->count())->toBe(1); + }); +}); + +describe('the one-to-one steal', function (): void { + it('names the holder when the picker asks whether a record can move', function (): void { + registerChipEntity(); + $definition = relatedPostsField(RelationshipCardinality::OneToOne); + + $target = Post::factory()->create(['title' => 'Aurora Labs']); + $holder = Post::factory()->create([ + 'title' => 'First Holder', + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + $conflict = app(CardinalityGuard::class)->violations( + $definition, + CustomFieldRelationship::DIRECTION_FROM, + Post::factory()->create()->getKey(), + [$target->getKey()], + ); + + expect($conflict)->toHaveCount(1) + ->and($conflict[0])->toContain('Aurora Labs') + ->and($conflict[0])->toContain('First Holder') + ->and($holder->fresh()->getCustomFieldValue($definition->fromField))->toBe([$target->getKey()]); + }); + + it('refuses the move until the payload carries the confirmation', function (): void { + registerChipEntity(); + $definition = relatedPostsField(RelationshipCardinality::OneToOne); + + $target = Post::factory()->create(['title' => 'Aurora Labs']); + Post::factory()->create([ + 'title' => 'First Holder', + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + $second = Post::factory()->create(['title' => 'Second Holder']); + + livewire(EditPost::class, ['record' => $second->getRouteKey()]) + ->set('data.custom_fields.'.$definition->fromField->code, [$target->getKey()]) + ->call('save') + ->assertHasFormErrors(['custom_fields.'.$definition->fromField->code]); + + expect(CustomFieldLink::query()->whereNull('active_until')->count())->toBe(1); + }); + + it('asks again for a second conflicting record after one has been confirmed', function (): void { + registerChipEntity(); + $definition = relatedPostsField(RelationshipCardinality::OneToOne); + + [$first, $second] = Post::factory()->count(2)->create(['title' => 'Target']); + + Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$first->getKey()]]]); + Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$second->getKey()]]]); + + $taker = Post::factory()->create(); + $guard = app(CardinalityGuard::class); + + // Confirming the first candidate is not an answer about the second: the guard is asked + // per candidate, and still refuses the one nobody confirmed. + expect($guard->violations($definition, CustomFieldRelationship::DIRECTION_FROM, $taker->getKey(), [$first->getKey()], confirmed: [(string) $first->getKey()])) + ->toBeEmpty() + ->and($guard->violations($definition, CustomFieldRelationship::DIRECTION_FROM, $taker->getKey(), [$second->getKey()])) + ->toHaveCount(1); + }); + + it('sends a flat id list when the payload no longer holds the confirmed record', function (): void { + registerChipEntity(); + $definition = relatedPostsField(RelationshipCardinality::ManyToMany); + + [$confirmed, $free] = Post::factory()->count(2)->create(); + $host = Post::factory()->create([ + 'custom_fields' => [ + $definition->fromField->code => ['ids' => [$confirmed->getKey()], 'replace' => true], + ], + ]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.'.$definition->fromField->code, [$free->getKey()]) + ->call('save') + ->assertHasNoFormErrors(); + + expect($host->fresh()->getCustomFieldValue($definition->fromField->fresh()))->toBe([$free->getKey()]); + }); + + it('carries the confirmation back into the form after a failed round trip', function (): void { + registerChipEntity(); + $definition = relatedPostsField(RelationshipCardinality::OneToOne); + + $target = Post::factory()->create(); + $host = Post::factory()->create([ + 'custom_fields' => [ + $definition->fromField->code => ['ids' => [$target->getKey()], 'replace' => true], + ], + ]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->assertSeeHtml('confirmedStealIds') + ->assertSeeHtml((string) $target->getKey()); + }); + + it('moves the record once the confirmation travels with the ids', function (): void { + registerChipEntity(); + $definition = relatedPostsField(RelationshipCardinality::OneToOne); + + $target = Post::factory()->create(['title' => 'Aurora Labs']); + $first = Post::factory()->create([ + 'title' => 'First Holder', + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + $second = Post::factory()->create(['title' => 'Second Holder']); + + livewire(EditPost::class, ['record' => $second->getRouteKey()]) + ->set('data.custom_fields.'.$definition->fromField->code, [ + 'ids' => [$target->getKey()], + 'replace' => true, + ]) + ->call('save') + ->assertHasNoFormErrors(); + + $reader = app(LinkReader::class); + + expect($reader->orderedIdsFor($second->fresh(), $definition, CustomFieldRelationship::DIRECTION_FROM)) + ->toBe([$target->getKey()]) + ->and($reader->orderedIdsFor($first->fresh(), $definition, CustomFieldRelationship::DIRECTION_FROM)) + ->toBe([]) + ->and(CustomFieldLink::query()->whereNotNull('active_until')->count())->toBe(1); + }); +}); diff --git a/tests/Feature/Relationships/RecordFieldConfigurationTest.php b/tests/Feature/Relationships/RecordFieldConfigurationTest.php new file mode 100644 index 00000000..e84c1cda --- /dev/null +++ b/tests/Feature/Relationships/RecordFieldConfigurationTest.php @@ -0,0 +1,233 @@ +postSection = CustomFieldSection::factory()->forEntityType(Post::class)->create(); +}); + +function mountFieldOfType(CustomFieldSection $section, string $type): Testable +{ + return livewire(ManageCustomFieldSection::class, [ + 'section' => $section, + 'entityType' => Post::class, + ]) + ->mountAction('createField') + ->set('mountedActions.0.data.type', $type) + ->set('mountedActions.0.data.name', 'Related Comment') + ->set('mountedActions.0.data.code', 'related_comment') + ->set('mountedActions.0.data.relationship.target_entity_type', Comment::class); +} + +/** + * What the user is actually offered: the two frames are mutually exclusive, so a hidden one + * takes its children out of this list. + * + * @return array + */ +function visibleRelationshipInputs(Testable $component): array +{ + $livewire = $component->instance(); + $schema = $livewire->getSchema($livewire->getMountedActionSchemaName()); + + $names = []; + + foreach ($schema?->getFlatComponents() ?? [] as $child) { + if ($child instanceof Field && str_starts_with($child->getName(), 'relationship.')) { + $names[] = $child->getName(); + } + } + + return $names; +} + +function visibleConfigurator(Testable $component): ?RelationshipConfigurator +{ + $livewire = $component->instance(); + + return $livewire + ->getSchema($livewire->getMountedActionSchemaName()) + ?->getComponent(fn (mixed $child): bool => $child instanceof RelationshipConfigurator); +} + +function oneWayComments(CustomFieldSection $section, RelationshipCardinality $cardinality): CustomFieldRelationship +{ + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_comment', + fromEntityType: Post::class, + toEntityType: Comment::class, + cardinality: $cardinality, + fromField: new FieldSlotData(name: 'Related Comment', sectionId: $section->getKey()), + )); +} + +describe('the record face', function (): void { + it('asks where the field points and how many records it holds, in both flavors', function (string $flavor): void { + config()->set('custom-fields.ui.flavor', $flavor); + + $component = mountFieldOfType($this->postSection, RecordFieldType::KEY); + + expect(visibleRelationshipInputs($component))->toBe([ + 'relationship.target_entity_type', + 'relationship.allow_multiple', + ])->and(visibleConfigurator($component))->toBeNull(); + })->with(['polished', 'native']); + + it('gives the relationship type the configurator the record type never shows', function (): void { + config()->set('custom-fields.ui.flavor', 'polished'); + + expect(visibleConfigurator(mountFieldOfType($this->postSection, RelationshipFieldType::KEY))) + ->toBeInstanceOf(RelationshipConfigurator::class) + ->and(visibleConfigurator(mountFieldOfType($this->postSection, RecordFieldType::KEY))) + ->toBeNull(); + }); + + it('writes a one-slot definition holding one record while multiple is off', function (): void { + mountFieldOfType($this->postSection, RecordFieldType::KEY) + ->callMountedAction() + ->assertHasNoActionErrors(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect($definition->cardinality)->toBe(RelationshipCardinality::ManyToOne) + ->and($definition->to_field_id)->toBeNull() + ->and($definition->is_symmetric)->toBeFalse() + ->and($definition->fromField->type)->toBe(RecordFieldType::KEY) + ->and($definition->fromField->allowsMultipleRecords())->toBeFalse(); + }); + + it('writes a one-slot definition holding many records while multiple is on', function (): void { + mountFieldOfType($this->postSection, RecordFieldType::KEY) + ->set('mountedActions.0.data.relationship.allow_multiple', true) + ->callMountedAction() + ->assertHasNoActionErrors(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect($definition->cardinality)->toBe(RelationshipCardinality::ManyToMany) + ->and($definition->to_field_id)->toBeNull() + ->and($definition->fromField->allowsMultipleRecords())->toBeTrue(); + }); + + it('fills the toggle from the cardinality the definition holds', function (): void { + $definition = oneWayComments($this->postSection, RelationshipCardinality::ManyToMany); + + livewire(ManageCustomField::class, ['field' => $definition->fromField]) + ->mountAction('edit') + ->assertActionDataSet([ + 'relationship.target_entity_type' => Comment::class, + 'relationship.allow_multiple' => true, + ]); + }); + + it('locks the entity the field points at once it exists', function (): void { + $definition = oneWayComments($this->postSection, RelationshipCardinality::ManyToOne); + + livewire(ManageCustomField::class, ['field' => $definition->fromField]) + ->mountAction('edit') + ->assertSchemaComponentExists( + 'relationship.target_entity_type', + checkComponentUsing: fn (Select $component): bool => $component->isDisabled(), + ); + }); + + it('confirms the keep-first before it stops holding many records', function (): void { + $definition = oneWayComments($this->postSection, RelationshipCardinality::ManyToMany); + $field = $definition->fromField; + [$first, $second] = Comment::factory()->count(2)->create(); + $post = Post::factory()->create(['custom_fields' => [$field->code => [$first->getKey(), $second->getKey()]]]); + + livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->set('mountedActions.0.data.relationship.allow_multiple', false) + ->assertSchemaComponentVisible('relationship.keep_first') + ->callMountedAction() + ->assertHasActionErrors(['relationship.keep_first']); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::ManyToMany); + + livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->set('mountedActions.0.data.relationship.allow_multiple', false) + ->set('mountedActions.0.data.relationship.keep_first', true) + ->callMountedAction() + ->assertHasNoActionErrors(); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::ManyToOne) + ->and($post->fresh()->getCustomFieldValue($field->fresh()))->toBe([$first->getKey()]) + ->and(CustomFieldLink::query()->whereNotNull('active_until')->count())->toBe(1); + }); + + it('keeps the far end where it is when a save only renames the field', function (string $cardinality): void { + $definition = oneWayComments($this->postSection, RelationshipCardinality::from($cardinality)); + + livewire(ManageCustomField::class, ['field' => $definition->fromField]) + ->mountAction('edit') + ->set('mountedActions.0.data.name', 'Renamed') + ->callMountedAction() + ->assertHasNoActionErrors(); + + expect($definition->refresh()->cardinality->value)->toBe($cardinality) + ->and($definition->fromField->name)->toBe('Renamed'); + })->with([ + RelationshipCardinality::OneToOne->value, + RelationshipCardinality::OneToMany->value, + RelationshipCardinality::ManyToOne->value, + RelationshipCardinality::ManyToMany->value, + ]); + + it('holds many records without freeing the end that holds one', function (): void { + $definition = oneWayComments($this->postSection, RelationshipCardinality::OneToOne); + + livewire(ManageCustomField::class, ['field' => $definition->fromField]) + ->mountAction('edit') + ->set('mountedActions.0.data.relationship.allow_multiple', true) + ->callMountedAction() + ->assertHasNoActionErrors(); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::OneToMany); + + livewire(ManageCustomField::class, ['field' => $definition->fromField->fresh()]) + ->mountAction('edit') + ->set('mountedActions.0.data.relationship.allow_multiple', false) + ->set('mountedActions.0.data.relationship.keep_first', true) + ->callMountedAction() + ->assertHasNoActionErrors(); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::OneToOne); + }); + + it('keeps every record when the field goes on holding many', function (): void { + $definition = oneWayComments($this->postSection, RelationshipCardinality::ManyToMany); + $field = $definition->fromField; + $comments = Comment::factory()->count(2)->create(); + $post = Post::factory()->create(['custom_fields' => [$field->code => $comments->modelKeys()]]); + + livewire(ManageCustomField::class, ['field' => $field]) + ->mountAction('edit') + ->assertSchemaComponentHidden('relationship.keep_first') + ->callMountedAction() + ->assertHasNoActionErrors(); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::ManyToMany) + ->and($post->fresh()->getCustomFieldValue($field->fresh()))->toBe($comments->modelKeys()); + }); +}); diff --git a/tests/Feature/Relationships/RecordFieldReadPathTest.php b/tests/Feature/Relationships/RecordFieldReadPathTest.php new file mode 100644 index 00000000..f15e5a03 --- /dev/null +++ b/tests/Feature/Relationships/RecordFieldReadPathTest.php @@ -0,0 +1,178 @@ +getMorphClass()); + + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'read_path_related', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: $cardinality, + fromField: new FieldSlotData(name: 'Related Posts', sectionId: $section->getKey()), + toField: new FieldSlotData(name: 'Related From', sectionId: $section->getKey()), + )); +} + +function linkQueryCount(callable $work): int +{ + $links = config('custom-fields.database.table_names.custom_field_links'); + + DB::flushQueryLog(); + DB::enableQueryLog(); + + try { + $work(); + + return count(array_filter( + DB::getQueryLog(), + static fn (array $entry): bool => str_contains($entry['query'], $links), + )); + } finally { + DB::disableQueryLog(); + DB::flushQueryLog(); + } +} + +it('reads ordered ids from both sides of one edge set', function (): void { + $definition = readPathRelated(); + $post = Post::factory()->create(); + [$a, $b] = Post::factory()->count(2)->create(); + + $post->update(['custom_fields' => [$definition->fromField->code => [$b->getKey(), $a->getKey()]]]); + + expect($post->refresh()->getCustomFieldValue($definition->fromField)) + ->toBe([$b->getKey(), $a->getKey()]) + ->and($a->refresh()->getCustomFieldValue($definition->toField)) + ->toBe([$post->getKey()]); +}); + +it('returns an empty array for an unlinked record field', function (): void { + $definition = readPathRelated(); + + expect(Post::factory()->create()->getCustomFieldValue($definition->fromField))->toBe([]); +}); + +it('reads both ends of a symmetric definition through its single field', function (): void { + $section = sectionForEntity((new Post)->getMorphClass()); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'read_path_sibling', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Siblings', sectionId: $section->getKey()), + )); + + $post = Post::factory()->create(); + $sibling = Post::factory()->create(); + + $post->update(['custom_fields' => [$definition->fromField->code => [$sibling->getKey()]]]); + + expect($post->refresh()->getCustomFieldValue($definition->fromField))->toBe([$sibling->getKey()]) + ->and($sibling->refresh()->getCustomFieldValue($definition->fromField))->toBe([$post->getKey()]); +}); + +it('resolves record field titles through the edge ledger', function (): void { + $definition = readPathRelated(); + $post = Post::factory()->create(); + $a = Post::factory()->create(['title' => 'Alpha']); + $b = Post::factory()->create(['title' => 'Beta']); + + $post->update(['custom_fields' => [$definition->fromField->code => [$b->getKey(), $a->getKey()]]]); + + app(LookupCache::class)->flush(); + + expect(app(ValueResolverInterface::class)->resolve($post->refresh(), $definition->fromField)) + ->toBe(['Beta', 'Alpha']); +}); + +it('batch loads links so reading a page of records costs one query per side', function (): void { + $definition = readPathRelated(); + $target = Post::factory()->create(); + + $hosts = Post::factory()->count(2)->create(); + $manyHosts = Post::factory()->count(6)->create(); + + foreach ($hosts->merge($manyHosts) as $host) { + $host->update(['custom_fields' => [$definition->fromField->code => [$target->getKey()]]]); + } + + $read = function (iterable $ids) use ($definition): void { + $records = Post::query()->whereIn('id', $ids)->withCustomFieldValues()->get(); + + foreach ($records as $record) { + $record->getCustomFieldValue($definition->fromField); + } + }; + + $read([$target->getKey()]); + + $small = linkQueryCount(fn (): mixed => $read($hosts->pluck('id'))); + $large = linkQueryCount(fn (): mixed => $read($manyHosts->pluck('id'))); + + expect($small)->toBe(2) + ->and($large)->toBe($small); +}); + +it('still eager loads links for a batch read while the relationships feature is off', function (): void { + $definition = readPathRelated(); + $target = Post::factory()->create(); + $host = Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$target->getKey()]]]); + + config('custom-fields.features')->disable(CustomFieldsFeature::SYSTEM_RELATIONSHIPS); + + $loaded = Post::query()->whereKey($host->getKey())->withCustomFieldValues()->sole(); + + expect($loaded->relationLoaded('outgoingLinks'))->toBeTrue() + ->and($loaded->getCustomFieldValue($definition->fromField))->toBe([$target->getKey()]); +}); + +it('resolves linked titles for a loaded page without a query per record', function (): void { + $definition = readPathRelated(); + $targets = Post::factory()->count(3)->create(); + + $hosts = Post::factory()->count(3)->create(); + + foreach ($hosts as $index => $host) { + $host->update(['custom_fields' => [$definition->fromField->code => [$targets[$index]->getKey()]]]); + } + + app(LookupCache::class)->flush(); + + $loaded = Post::query()->whereIn('id', $hosts->pluck('id'))->withCustomFieldValues()->get(); + + DB::flushQueryLog(); + DB::enableQueryLog(); + + try { + $titles = $loaded->map(fn (Post $host): array => app(ValueResolverInterface::class) + ->resolve($host, $definition->fromField)); + + $targetQueries = count(array_filter( + DB::getQueryLog(), + static fn (array $entry): bool => str_contains($entry['query'], '"posts"') + || str_contains($entry['query'], '`posts`'), + )); + + expect($targetQueries)->toBe(0) + ->and($titles->all())->toBe($targets->map(fn (Post $target): array => [$target->title])->all()); + } finally { + DB::disableQueryLog(); + DB::flushQueryLog(); + } +}); diff --git a/tests/Feature/Relationships/RecordFieldWritePathTest.php b/tests/Feature/Relationships/RecordFieldWritePathTest.php new file mode 100644 index 00000000..41ef4f3b --- /dev/null +++ b/tests/Feature/Relationships/RecordFieldWritePathTest.php @@ -0,0 +1,195 @@ +execute(new RelationshipDefinitionData( + code: 'write_path_authorship', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: $cardinality, + fromField: new FieldSlotData(name: 'Author', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + )); +} + +/** + * @return array + */ +function activeTargetIds(): array +{ + return CustomFieldLink::query() + ->active() + ->orderBy('sort_order') + ->pluck('to_entity_id') + ->map(intval(...)) + ->all(); +} + +it('writes links instead of a value row when a create payload names a record field', function (): void { + $definition = writePathAuthorship(); + $user = User::factory()->create(); + + Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$user->getKey()]]]); + + expect(activeTargetIds())->toBe([$user->getKey()]) + ->and(CustomFieldValue::query()->where('custom_field_id', $definition->from_field_id)->count())->toBe(0); +}); + +it('replaces the edge set when an update payload changes it', function (): void { + $definition = writePathAuthorship(); + $code = $definition->fromField->code; + [$a, $b] = User::factory()->count(2)->create(); + + $post = Post::factory()->create(['custom_fields' => [$code => [$a->getKey()]]]); + + $post->update(['custom_fields' => [$code => [$b->getKey()]]]); + + expect(activeTargetIds())->toBe([$b->getKey()]) + ->and(CustomFieldLink::query()->whereNotNull('active_until')->count())->toBe(1); +}); + +it('closes every edge when the payload holds an empty value', function (): void { + $definition = writePathAuthorship(); + $code = $definition->fromField->code; + $user = User::factory()->create(); + + $post = Post::factory()->create(['custom_fields' => [$code => [$user->getKey()]]]); + + $post->update(['custom_fields' => [$code => []]]); + + expect(activeTargetIds())->toBe([]) + ->and(CustomFieldLink::query()->count())->toBe(1); +}); + +it('closes every edge when the payload holds null', function (): void { + $definition = writePathAuthorship(); + $code = $definition->fromField->code; + $user = User::factory()->create(); + + $post = Post::factory()->create(['custom_fields' => [$code => [$user->getKey()]]]); + + $post->update(['custom_fields' => [$code => null]]); + + expect(activeTargetIds())->toBe([]); +}); + +it('leaves the edges alone when the payload omits the record field', function (): void { + $definition = writePathAuthorship(); + $code = $definition->fromField->code; + $user = User::factory()->create(); + + $post = Post::factory()->create(['custom_fields' => [$code => [$user->getKey()]]]); + + $post->update(['custom_fields' => []]); + + expect(activeTargetIds())->toBe([$user->getKey()]); +}); + +it('writes links for a record field with a definition even while the relationships feature is off', function (): void { + $definition = writePathAuthorship(); + $code = $definition->fromField->code; + $user = User::factory()->create(); + + config('custom-fields.features')->disable(CustomFieldsFeature::SYSTEM_RELATIONSHIPS); + + $post = Post::factory()->create(['custom_fields' => [$code => [$user->getKey()]]]); + + expect(activeTargetIds())->toBe([$user->getKey()]) + ->and(CustomFieldValue::query()->where('custom_field_id', $definition->from_field_id)->count())->toBe(0) + ->and($post->refresh()->getCustomFieldValue($definition->fromField))->toBe([$user->getKey()]); +}); + +it('keeps an undefined record field on the value row while the relationships feature is off', function (): void { + $field = CustomField::factory()->create([ + 'code' => 'write_path_unbound', + 'type' => 'record', + 'entity_type' => (new Post)->getMorphClass(), + 'custom_field_section_id' => sectionForEntity((new Post)->getMorphClass())->getKey(), + ]); + $user = User::factory()->create(); + + config('custom-fields.features')->disable(CustomFieldsFeature::SYSTEM_RELATIONSHIPS); + + Post::factory()->create(['custom_fields' => [$field->code => [$user->getKey()]]]); + + expect(CustomFieldLink::query()->count())->toBe(0) + ->and(CustomFieldValue::query()->where('custom_field_id', $field->getKey())->count())->toBe(1); +}); + +it('stamps the definition tenant on links written through the trait', function (): void { + useTenantSchema(7); + + $definition = writePathAuthorship(); + $user = User::factory()->create(); + + Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$user->getKey()]]]); + + expect(CustomFieldLink::query()->sole()->tenant_id)->toBe(7); +})->skip( + fn (): bool => DB::connection()->getDriverName() === 'mysql', + 'MySQL commits DDL implicitly, so the added tenant columns would outlive the test transaction.', +); + +it('rolls the new record back when a link target is rejected', function (): void { + $definition = writePathAuthorship(); + + $before = Post::query()->count(); + + expect(fn (): Post => Post::factory()->create([ + 'custom_fields' => [$definition->fromField->code => [999999]], + ]))->toThrow(ValidationException::class); + + expect(Post::query()->count())->toBe($before) + ->and(CustomFieldLink::query()->count())->toBe(0); +}); + +it('rolls an update back when a link target is rejected', function (): void { + $definition = writePathAuthorship(); + $code = $definition->fromField->code; + $user = User::factory()->create(); + + $post = Post::factory()->create(['custom_fields' => [$code => [$user->getKey()]], 'title' => 'Kept']); + + expect(fn (): bool => $post->update([ + 'title' => 'Rolled back', + 'custom_fields' => [$code => [999999]], + ]))->toThrow(ValidationException::class); + + expect($post->fresh()->title)->toBe('Kept') + ->and(activeTargetIds())->toBe([$user->getKey()]); +}); + +it('still clears a non-record field when the payload omits its key', function (string $type, mixed $value): void { + $field = CustomField::factory()->create([ + 'code' => 'omitted_'.$type, + 'type' => $type, + 'entity_type' => (new Post)->getMorphClass(), + 'custom_field_section_id' => sectionForEntity((new Post)->getMorphClass())->getKey(), + ]); + + $post = Post::factory()->create(['custom_fields' => ['omitted_'.$type => $value]]); + + expect(Post::query()->findOrFail($post->getKey())->getCustomFieldValue($field))->toBe($value); + + $post->update(['custom_fields' => []]); + + expect(Post::query()->findOrFail($post->getKey())->getCustomFieldValue($field))->toBeNull(); +})->with([ + 'text' => ['text', 'kept'], + 'select' => ['select', 7], +]); diff --git a/tests/Feature/Relationships/RecordSurfacesTest.php b/tests/Feature/Relationships/RecordSurfacesTest.php new file mode 100644 index 00000000..19122648 --- /dev/null +++ b/tests/Feature/Relationships/RecordSurfacesTest.php @@ -0,0 +1,288 @@ +set('custom-fields.entity_configuration', + EntityConfigurator::configure() + ->autoDiscover(false) + ->cache(false) + ->models([ + EntityModel::configure( + modelClass: Post::class, + labelSingular: 'Post', + primaryAttribute: 'title', + searchAttributes: ['title'], + resourceClass: PostResource::class, + features: [EntityFeature::CUSTOM_FIELDS, EntityFeature::LOOKUP_SOURCE], + avatarConfiguration: new AvatarConfiguration(attribute: 'content'), + ), + ]) + ); + + app()->forgetInstance(EntityManager::class); +}); + +function linkedPostsField( + string $type, + RelationshipCardinality $cardinality = RelationshipCardinality::ManyToMany, +): CustomFieldRelationship { + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'linked_posts_'.$type, + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: $cardinality, + fromField: new FieldSlotData( + name: 'Linked Posts', + sectionId: sectionForEntity((new Post)->getMorphClass())->getKey(), + type: $type, + ), + )); +} + +function linkTableQueries(callable $work): int +{ + $links = config('custom-fields.database.table_names.custom_field_links'); + + DB::flushQueryLog(); + DB::enableQueryLog(); + + try { + $work(); + + return count(array_filter( + DB::getQueryLog(), + static fn (array $entry): bool => str_contains($entry['query'], $links), + )); + } finally { + DB::disableQueryLog(); + DB::flushQueryLog(); + } +} + +describe('the surfaces each type draws', function (): void { + it('shows the linked record on the table and the record page for both types', function (string $type, string $flavor): void { + config()->set('custom-fields.ui.flavor', $flavor); + + $definition = linkedPostsField($type); + $target = Post::factory()->create(['title' => 'Aurora Labs']); + $host = Post::factory()->create([ + 'title' => 'Holder', + 'custom_fields' => [$definition->fromField->code => [$target->getKey()]], + ]); + + // Chips are the paired type's face, and only where the polished flavor draws them. + $drawsChips = $type === RelationshipFieldType::KEY && rendersPolished(UiSurface::RecordChips); + + $table = livewire(ListPosts::class)->assertSee('Aurora Labs'); + + $drawsChips + ? $table->assertSeeHtml('data-surface="record-chips"') + : $table->assertDontSeeHtml('data-surface="record-chips"'); + + $page = livewire(ViewPost::class, ['record' => $host->getRouteKey()])->assertSee('Aurora Labs'); + + $entry = Schema::make($page->instance()) + ->record($host) + ->components([app(RecordEntry::class)->make($definition->fromField, $host)]) + ->getComponent(fn (mixed $component): bool => $component instanceof ViewEntry); + + expect($entry?->getState()['chipsView']) + ->toBe($drawsChips ? 'custom-fields::flavors.polished.record-chips' : null); + })->with([RecordFieldType::KEY, RelationshipFieldType::KEY])->with(['polished', 'native']); + + it('edits a record field through the plain select, without the move confirmation', function (string $flavor): void { + config()->set('custom-fields.ui.flavor', $flavor); + + $definition = linkedPostsField(RecordFieldType::KEY); + $target = Post::factory()->create(['title' => 'Aurora Labs']); + $host = Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$target->getKey()]]]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->assertSee('Aurora Labs') + ->assertDontSeeHtml('data-surface="record-picker"') + ->assertDontSee('Move this record?') + ->assertDontSee('Create a new Post'); + })->with(['polished', 'native']); + + it('edits a relationship field through the picker that confirms a move', function (): void { + config()->set('custom-fields.ui.flavor', 'polished'); + + $definition = linkedPostsField(RelationshipFieldType::KEY); + $host = Post::factory()->create(); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->assertSeeHtml('data-surface="record-picker"') + ->assertSee('Move this record?') + ->assertSee('Create a new Post'); + }); + + it('reads the links of a record column in a fixed number of queries', function (): void { + $definition = linkedPostsField(RecordFieldType::KEY); + $code = $definition->fromField->code; + $linked = 0; + + $listQueries = function (int $rows) use ($code, &$linked): int { + while ($linked < $rows) { + Post::factory()->create([ + 'custom_fields' => [$code => [Post::factory()->create()->getKey()]], + ]); + + $linked++; + } + + return linkTableQueries(fn () => livewire(ListPosts::class)->assertSuccessful()); + }; + + // The first render warms what the page reads once; from there the ledger is read in + // one pass however many rows carry a link. + $listQueries(1); + + expect($listQueries(2))->toBe($listQueries(4)); + }); + + it('leaves a record field out of the paired rows the attribute table connects', function (): void { + linkedPostsField(RecordFieldType::KEY); + + expect(livewire(ManageFieldsTable::class, ['entityType' => Post::class])->instance()->relationshipPairs()) + ->toBe([]); + }); +}); + +describe('a record field on a single side', function (): void { + it('reports the holder instead of offering the move', function (): void { + $definition = linkedPostsField(RecordFieldType::KEY, RelationshipCardinality::OneToOne); + $code = $definition->fromField->code; + + $target = Post::factory()->create(['title' => 'Aurora Labs']); + Post::factory()->create(['title' => 'First Holder', 'custom_fields' => [$code => [$target->getKey()]]]); + $second = Post::factory()->create(['title' => 'Second Holder']); + + livewire(EditPost::class, ['record' => $second->getRouteKey()]) + ->set('data.custom_fields.'.$code, [$target->getKey()]) + ->call('save') + ->assertHasFormErrors(['custom_fields.'.$code]); + + expect(CustomFieldLink::query()->active()->count())->toBe(1); + }); + + it('takes the confirmed move a host sends through the api form of the payload', function (): void { + $definition = linkedPostsField(RecordFieldType::KEY, RelationshipCardinality::OneToOne); + $code = $definition->fromField->code; + + $target = Post::factory()->create(['title' => 'Aurora Labs']); + Post::factory()->create(['title' => 'First Holder', 'custom_fields' => [$code => [$target->getKey()]]]); + $second = Post::factory()->create(['title' => 'Second Holder']); + + livewire(EditPost::class, ['record' => $second->getRouteKey()]) + ->set('data.custom_fields.'.$code, ['ids' => [$target->getKey()], 'replace' => true]) + ->call('save') + ->assertHasNoFormErrors(); + + expect(app(LinkReader::class)->orderedIdsFor($second->fresh(), $definition, CustomFieldRelationship::DIRECTION_FROM)) + ->toBe([$target->getKey()]); + }); +}); + +describe('a confirmation in a payload of several records', function (): void { + it('answers only for the record it names', function (): void { + $definition = linkedPostsField(RelationshipFieldType::KEY, RelationshipCardinality::OneToMany); + $code = $definition->fromField->code; + + [$taken, $free, $confirmed] = Post::factory()->count(3)->create(); + Post::factory()->create(['custom_fields' => [$code => [$taken->getKey()]]]); + Post::factory()->create(['custom_fields' => [$code => [$confirmed->getKey()]]]); + + $host = Post::factory()->create(); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.'.$code, [ + 'ids' => [$taken->getKey(), $free->getKey(), $confirmed->getKey()], + 'confirmed' => [(string) $confirmed->getKey()], + ]) + ->call('save') + ->assertHasFormErrors(['custom_fields.'.$code]); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.'.$code, [ + 'ids' => [$taken->getKey(), $free->getKey(), $confirmed->getKey()], + 'confirmed' => [(string) $taken->getKey(), (string) $confirmed->getKey()], + ]) + ->call('save') + ->assertHasNoFormErrors(); + + expect(app(LinkReader::class)->orderedIdsFor($host->fresh(), $definition, CustomFieldRelationship::DIRECTION_FROM)) + ->toBe([$taken->getKey(), $free->getKey(), $confirmed->getKey()]); + }); + + it('carries only the named record back into the picker after a failed round trip', function (): void { + $definition = linkedPostsField(RelationshipFieldType::KEY, RelationshipCardinality::OneToMany); + $code = $definition->fromField->code; + + [$taken, $confirmed] = Post::factory()->count(2)->create(); + Post::factory()->create(['custom_fields' => [$code => [$taken->getKey()]]]); + Post::factory()->create(['custom_fields' => [$code => [$confirmed->getKey()]]]); + + $host = Post::factory()->create(); + + $html = livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.'.$code, [ + 'ids' => [$taken->getKey(), $confirmed->getKey()], + 'confirmed' => [(string) $confirmed->getKey()], + ]) + ->call('save') + ->assertHasFormErrors(['custom_fields.'.$code]) + ->html(); + + expect($html)->toContain('confirmedStealIds: '.Js::from([(string) $confirmed->getKey()])->toHtml()) + ->and($html)->not->toContain('confirmedStealIds: '.Js::from([(string) $taken->getKey()])->toHtml()); + }); + + it('drops the confirmation once the record it names leaves the payload', function (): void { + $definition = linkedPostsField(RelationshipFieldType::KEY, RelationshipCardinality::OneToMany); + $code = $definition->fromField->code; + + [$taken, $confirmed] = Post::factory()->count(2)->create(); + Post::factory()->create(['custom_fields' => [$code => [$taken->getKey()]]]); + Post::factory()->create(['custom_fields' => [$code => [$confirmed->getKey()]]]); + + $host = Post::factory()->create(); + + livewire(EditPost::class, ['record' => $host->getRouteKey()]) + ->set('data.custom_fields.'.$code, [ + 'ids' => [$taken->getKey()], + 'confirmed' => [(string) $confirmed->getKey()], + ]) + ->call('save') + ->assertHasFormErrors(['custom_fields.'.$code]); + + expect(CustomFieldLink::query()->active()->count())->toBe(2); + }); +}); diff --git a/tests/Feature/Relationships/RecordTableSurfacesTest.php b/tests/Feature/Relationships/RecordTableSurfacesTest.php new file mode 100644 index 00000000..ce3abec8 --- /dev/null +++ b/tests/Feature/Relationships/RecordTableSurfacesTest.php @@ -0,0 +1,171 @@ +execute(new RelationshipDefinitionData( + code: 'table_surface_related', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: $cardinality, + fromField: new FieldSlotData(name: 'Related Post', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + )); +} + +it('filters posts by several record ids at once', function (): void { + $definition = tableSurfaceDefinition(RelationshipCardinality::ManyToMany); + $code = $definition->fromField->code; + + [$first, $second, $third] = Post::factory()->count(3)->create(); + + $matchesFirst = Post::factory()->create(['custom_fields' => [$code => [$first->getKey()]]]); + $matchesSecond = Post::factory()->create(['custom_fields' => [$code => [$second->getKey()]]]); + $matchesThird = Post::factory()->create(['custom_fields' => [$code => [$third->getKey()]]]); + + livewire(ListPosts::class) + ->set(sprintf('tableFilters.custom_fields.%s.values', $code), [$first->getKey(), $second->getKey()]) + ->assertCanSeeTableRecords([$matchesFirst, $matchesSecond]) + ->assertCanNotSeeTableRecords([$matchesThird]); +}); + +it('reads a record field from the to side of a paired definition', function (): void { + registerPostLookupEntity(); + $section = sectionForEntity((new Post)->getMorphClass()); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'table_surface_paired', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Mentions', sectionId: $section->getKey()), + toField: new FieldSlotData(name: 'Mentioned By', sectionId: $section->getKey()), + )); + + $mentioned = Post::factory()->create(); + $mentions = Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$mentioned->getKey()]]]); + $unrelated = Post::factory()->create(); + + livewire(ListPosts::class) + ->set(sprintf('tableFilters.custom_fields.%s.values', $definition->toField->code), [$mentions->getKey()]) + ->assertCanSeeTableRecords([$mentioned]) + ->assertCanNotSeeTableRecords([$mentions, $unrelated]); +}); + +it('sorts posts by the linked record title and puts unlinked rows last', function (): void { + $definition = tableSurfaceDefinition(RelationshipCardinality::ManyToOne); + $code = $definition->fromField->code; + + $alpha = Post::factory()->create(['title' => 'Alpha']); + $bravo = Post::factory()->create(['title' => 'Bravo']); + $charlie = Post::factory()->create(['title' => 'Charlie']); + $unlinked = Post::factory()->create(['title' => 'Delta']); + + $alpha->update(['custom_fields' => [$code => [$charlie->getKey()]]]); + $bravo->update(['custom_fields' => [$code => [$alpha->getKey()]]]); + $charlie->update(['custom_fields' => [$code => [$bravo->getKey()]]]); + + livewire(ListPosts::class) + ->sortTable('custom_fields.'.$code, 'asc') + ->assertCanSeeTableRecords([$bravo, $charlie, $alpha, $unlinked], inOrder: true) + ->sortTable('custom_fields.'.$code, 'desc') + ->assertCanSeeTableRecords([$alpha, $charlie, $bravo, $unlinked], inOrder: true); +}); + +it('filters, sorts, and searches a symmetric definition from either end', function (): void { + registerPostLookupEntity(); + + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'table_surface_sibling', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::OneToOne, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Sibling', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + )); + + $definition->fromField->update(['settings' => new CustomFieldSettingsData(searchable: true)]); + + $code = $definition->fromField->code; + + $left = Post::factory()->create(['title' => 'Aaa Left']); + $right = Post::factory()->create(['title' => 'Zzz Right']); + $lonely = Post::factory()->create(['title' => 'Mmm Lonely']); + + $left->update(['custom_fields' => [$code => [$right->getKey()]]]); + + livewire(ListPosts::class) + ->set(sprintf('tableFilters.custom_fields.%s.values', $code), [$right->getKey()]) + ->assertCanSeeTableRecords([$left]) + ->assertCanNotSeeTableRecords([$lonely]); + + livewire(ListPosts::class) + ->set(sprintf('tableFilters.custom_fields.%s.values', $code), [$left->getKey()]) + ->assertCanSeeTableRecords([$right]) + ->assertCanNotSeeTableRecords([$lonely]); + + livewire(ListPosts::class) + ->sortTable('custom_fields.'.$code, 'asc') + ->assertCanSeeTableRecords([$right, $left, $lonely], inOrder: true); + + livewire(ListPosts::class) + ->searchTable('Zzz') + ->assertCanSeeTableRecords([$left, $right]) + ->assertCanNotSeeTableRecords([$lonely]); +}); + +it('searches posts by the linked record title', function (): void { + $definition = tableSurfaceDefinition(RelationshipCardinality::ManyToOne); + $definition->fromField->update(['settings' => new CustomFieldSettingsData(searchable: true)]); + + $code = $definition->fromField->code; + + $target = Post::factory()->create(['title' => 'Findable Target']); + $other = Post::factory()->create(['title' => 'Unrelated Target']); + + $match = Post::factory()->create(['title' => 'Host one', 'custom_fields' => [$code => [$target->getKey()]]]); + $miss = Post::factory()->create(['title' => 'Host two', 'custom_fields' => [$code => [$other->getKey()]]]); + + livewire(ListPosts::class) + ->searchTable('findable') + ->assertCanSeeTableRecords([$match]) + ->assertCanNotSeeTableRecords([$miss, $other]); +}); + +it('hydrates and saves a record field through the panel form', function (): void { + $definition = tableSurfaceDefinition(RelationshipCardinality::ManyToOne); + $code = $definition->fromField->code; + + $first = Post::factory()->create(['title' => 'First Target']); + $second = Post::factory()->create(['title' => 'Second Target']); + $post = Post::factory()->create(['custom_fields' => [$code => [$first->getKey()]]]); + + livewire(EditPost::class, ['record' => $post->getRouteKey()]) + ->assertFormSet(['custom_fields' => [$code => [$first->getKey()]]]) + ->fillForm([ + 'title' => $post->title, + 'author_id' => $post->author_id, + 'rating' => $post->rating, + 'custom_fields' => [$code => [$second->getKey()]], + ]) + ->call('save') + ->assertHasNoFormErrors(); + + expect($post->refresh()->getCustomFieldValue($definition->fromField))->toBe([$second->getKey()]) + ->and(CustomFieldLink::query()->active()->count())->toBe(1) + ->and(CustomFieldValue::query()->where('custom_field_id', $definition->from_field_id)->count())->toBe(0); +}); diff --git a/tests/Feature/Relationships/RelationshipConfiguratorTest.php b/tests/Feature/Relationships/RelationshipConfiguratorTest.php new file mode 100644 index 00000000..77b17080 --- /dev/null +++ b/tests/Feature/Relationships/RelationshipConfiguratorTest.php @@ -0,0 +1,449 @@ +postSection = CustomFieldSection::factory()->forEntityType(Post::class)->create(); + $this->commentSection = CustomFieldSection::factory()->forEntityType(Comment::class)->create(); +}); + +function mountRelationshipField(CustomFieldSection $section, ?string $targetEntityType = Comment::class): Testable +{ + $component = livewire(ManageCustomFieldSection::class, [ + 'section' => $section, + 'entityType' => Post::class, + ]) + ->mountAction('createField') + ->set('mountedActions.0.data.type', RelationshipFieldType::KEY) + ->set('mountedActions.0.data.name', 'Related Comment') + ->set('mountedActions.0.data.code', 'related_comment'); + + return $component->set('mountedActions.0.data.relationship.target_entity_type', $targetEntityType); +} + +/** + * A host whose entity labels are written for Filament's sentence use, where "Create + * opportunity" is right and an entity card heading reading "opportunity" is not. + */ +function registerLowercaseLabelledEntities(): void +{ + config()->set('custom-fields.entity_configuration', + EntityConfigurator::configure() + ->autoDiscover(false) + ->cache(false) + ->models([ + EntityModel::configure( + modelClass: Post::class, + labelSingular: 'post', + labelPlural: 'posts', + features: [EntityFeature::CUSTOM_FIELDS, EntityFeature::LOOKUP_SOURCE], + ), + EntityModel::configure( + modelClass: Comment::class, + labelSingular: 'comment', + labelPlural: 'comments', + features: [EntityFeature::CUSTOM_FIELDS, EntityFeature::LOOKUP_SOURCE], + ), + ]) + ); + + app()->forgetInstance(EntityManager::class); +} + +function mountedConfigurator(Testable $component): ?RelationshipConfigurator +{ + $livewire = $component->instance(); + + return $livewire + ->getSchema($livewire->getMountedActionSchemaName()) + ?->getComponent(fn (mixed $component): bool => $component instanceof RelationshipConfigurator, withHidden: true); +} + +describe('flavors', function (): void { + it('frames the record configuration in the polished configurator', function (): void { + config()->set('custom-fields.ui.flavor', 'polished'); + + $configurator = mountedConfigurator(mountRelationshipField($this->postSection)); + + expect($configurator)->not->toBeNull() + ->and(array_keys($configurator->getConfiguredFields()))->toBe([ + 'relationship.target_entity_type', + 'relationship.cardinality', + 'relationship.paired_field_name', + ]); + }); + + it('keeps the stock fieldset in the native flavor', function (): void { + config()->set('custom-fields.ui.flavor_overrides', ['relationship-configurator' => 'native']); + + $component = mountRelationshipField($this->postSection); + + expect(mountedConfigurator($component))->toBeNull(); + + $component->assertSchemaComponentExists('relationship.target_entity_type') + ->assertSchemaComponentExists('relationship.cardinality'); + }); + + it('stores the same definition whichever flavor collected it', function (string $flavor): void { + config()->set('custom-fields.ui.flavor', $flavor); + + mountRelationshipField($this->postSection) + ->set('mountedActions.0.data.relationship.cardinality', RelationshipCardinality::ManyToMany->value) + ->callMountedAction() + ->assertHasNoActionErrors(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect($definition->cardinality)->toBe(RelationshipCardinality::ManyToMany) + ->and($definition->from_entity_type)->toBe(Post::class) + ->and($definition->to_entity_type)->toBe(Comment::class); + })->with(['polished', 'native']); +}); + +describe('create defaults', function (): void { + it('opens the relationship configuration on a target and a cardinality', function (): void { + $component = livewire(ManageCustomFieldSection::class, [ + 'section' => $this->postSection, + 'entityType' => Post::class, + ]) + ->mountAction('createField') + ->set('mountedActions.0.data.type', RelationshipFieldType::KEY); + + expect($component->get('mountedActions.0.data.entity_type'))->toBe(Post::class) + ->and($component->get('mountedActions.0.data.relationship.target_entity_type'))->toBe(Post::class) + ->and($component->get('mountedActions.0.data.relationship.cardinality')) + ->toBe(RelationshipCardinality::ManyToOne->value); + }); + + it('writes the definition those defaults describe without a second choice', function (): void { + livewire(ManageCustomFieldSection::class, [ + 'section' => $this->postSection, + 'entityType' => Post::class, + ]) + ->mountAction('createField') + ->set('mountedActions.0.data.type', RelationshipFieldType::KEY) + ->set('mountedActions.0.data.name', 'Related Post') + ->set('mountedActions.0.data.code', 'related_post') + ->callMountedAction() + ->assertHasNoActionErrors(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect($definition->cardinality)->toBe(RelationshipCardinality::ManyToOne) + ->and($definition->from_entity_type)->toBe(Post::class) + ->and($definition->to_entity_type)->toBe(Post::class); + }); +}); + +describe('the cardinality sentence', function (): void { + // The sentence is the polished configurator's own reading of the state, so this block + // asserts one flavor whichever one the run is configured for. + beforeEach(fn () => config()->set('custom-fields.ui.flavor', 'polished')); + + it('names both ends in the words the cardinality means', function (string $cardinality, string $sentence): void { + $component = mountRelationshipField($this->postSection) + ->set('mountedActions.0.data.relationship.cardinality', $cardinality); + + expect(mountedConfigurator($component)?->getCardinalitySentence())->toBe($sentence); + })->with([ + [RelationshipCardinality::OneToOne->value, 'One Post links to one Comment.'], + [RelationshipCardinality::OneToMany->value, 'One Post links to many Comments.'], + [RelationshipCardinality::ManyToOne->value, 'Many Posts link to one Comment.'], + [RelationshipCardinality::ManyToMany->value, 'Many Posts link to many Comments.'], + ]); + + it('opens each name with a capital however the host cased its labels', function (string $cardinality, string $sentence): void { + registerLowercaseLabelledEntities(); + + $component = mountRelationshipField($this->postSection) + ->set('mountedActions.0.data.relationship.cardinality', $cardinality); + + expect(mountedConfigurator($component)?->getCardinalitySentence())->toBe($sentence); + })->with([ + [RelationshipCardinality::OneToOne->value, 'One Post links to one Comment.'], + [RelationshipCardinality::ManyToOne->value, 'Many Posts link to one Comment.'], + [RelationshipCardinality::OneToMany->value, 'One Post links to many Comments.'], + ]); + + it('has no sentence to read until both ends are chosen', function (): void { + $component = mountRelationshipField($this->postSection, targetEntityType: null) + ->set('mountedActions.0.data.relationship.cardinality', RelationshipCardinality::ManyToOne->value); + + expect(mountedConfigurator($component)?->getCardinalitySentence())->toBeNull(); + }); + + it('mirrors the field name from the shared grid rather than asking for it twice', function (): void { + $configurator = mountedConfigurator(mountRelationshipField($this->postSection)); + + expect($configurator?->getFieldName())->toBe('Related Comment') + ->and($configurator?->getFieldNameStatePath())->toBe('mountedActions.0.data.name'); + }); +}); + +describe('polished markup', function (): void { + beforeEach(fn () => config()->set('custom-fields.ui.flavor', 'polished')); + + it('renders both entity cards, the sync banner and the sentence', function (): void { + $html = view('custom-fields::flavors.polished.relationship-configurator', [ + 'attributes' => new ComponentAttributeBag, + 'getId' => fn (): string => 'configurator', + 'getExtraAttributes' => fn (): array => [], + 'getConfiguredFields' => fn (): array => [], + 'getSourceEntity' => fn () => Entities::getEntity(Post::class), + 'getTargetEntity' => fn () => Entities::getEntity(Comment::class), + 'getCardinalitySentence' => fn (): string => 'Many Posts link to one Comment.', + 'getFieldName' => fn (): string => 'Related Comment', + 'getFieldNameStatePath' => fn (): string => 'data.name', + 'isSymmetric' => fn (): bool => false, + 'pairsAField' => fn (): bool => false, + ])->render(); + + expect($html) + ->toContain('data-surface="relationship-configurator"') + ->toContain('data-flavor="polished"') + ->toContain('This entity') + ->toContain('Related entity') + ->toContain('Many Posts link to one Comment.') + ->toContain('One-way field') + ->toContain('Related Comment') + ->toContain('dark:'); + }); + + it('renders the shared children inside the polished frame', function (): void { + // The error bag is a request-scoped view variable a field wrapper reads, and the + // schema is rendered here outside one. + view()->share('errors', new ViewErrorBag); + + $configurator = mountedConfigurator( + mountRelationshipField($this->postSection) + ->set('mountedActions.0.data.relationship.cardinality', RelationshipCardinality::ManyToOne->value) + ); + + $html = (string) $configurator?->toHtml(); + + expect($html) + ->toContain('data-surface="relationship-configurator"') + ->toContain('Many Posts link to one Comment.') + ->toContain('relationship.target_entity_type') + ->toContain('relationship.cardinality') + ->toContain('relationship.paired_field_name'); + }); + + it('heads each entity card with a capital however the host cased its labels', function (): void { + registerLowercaseLabelledEntities(); + + $html = view('custom-fields::flavors.polished.relationship-configurator', [ + 'attributes' => new ComponentAttributeBag, + 'getId' => fn (): string => 'configurator', + 'getExtraAttributes' => fn (): array => [], + 'getConfiguredFields' => fn (): array => [], + 'getSourceEntity' => fn () => Entities::getEntity(Post::class), + 'getTargetEntity' => fn () => Entities::getEntity(Comment::class), + 'getCardinalitySentence' => fn (): ?string => null, + 'getFieldName' => fn (): string => 'Related Comment', + 'getFieldNameStatePath' => fn (): string => 'data.name', + 'isSymmetric' => fn (): bool => false, + 'pairsAField' => fn (): bool => false, + ])->render(); + + $compact = (string) preg_replace(['/>\s+/', '/\s+', '<'], $html); + + expect($compact) + ->toContain('>Post<') + ->toContain('>Comment<') + ->not->toContain('>post<') + ->not->toContain('>comment<'); + }); + + it('says the two fields stay in sync once the other side is named', function (): void { + $html = view('custom-fields::flavors.polished.relationship-configurator', [ + 'attributes' => new ComponentAttributeBag, + 'getId' => fn (): string => 'configurator', + 'getExtraAttributes' => fn (): array => [], + 'getConfiguredFields' => fn (): array => [], + 'getSourceEntity' => fn () => Entities::getEntity(Post::class), + 'getTargetEntity' => fn () => Entities::getEntity(Post::class), + 'getCardinalitySentence' => fn (): ?string => null, + 'getFieldName' => fn (): string => 'Related Post', + 'getFieldNameStatePath' => fn (): string => 'data.name', + 'isSymmetric' => fn (): bool => true, + 'pairsAField' => fn (): bool => false, + ])->render(); + + expect($html) + ->toContain('One field read from both ends') + ->toContain('Pick a related entity to see how the two sides connect.'); + }); +}); + +describe('submission', function (): void { + it('stores the definition the sentence describes', function (string $cardinality): void { + mountRelationshipField($this->postSection) + ->set('mountedActions.0.data.relationship.cardinality', $cardinality) + ->callMountedAction() + ->assertHasNoActionErrors(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect($definition->cardinality->value)->toBe($cardinality) + ->and($definition->to_field_id)->toBeNull(); + })->with([ + RelationshipCardinality::OneToOne->value, + RelationshipCardinality::OneToMany->value, + RelationshipCardinality::ManyToOne->value, + RelationshipCardinality::ManyToMany->value, + ]); + + it('adds the paired field the related-entity card names', function (): void { + mountRelationshipField($this->postSection) + ->set('mountedActions.0.data.relationship.cardinality', RelationshipCardinality::ManyToMany->value) + ->set('mountedActions.0.data.relationship.paired_field_name', 'Related Post') + ->set('mountedActions.0.data.relationship.paired_section_id', $this->commentSection->getKey()) + ->callMountedAction() + ->assertHasNoActionErrors(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect($definition->toField->name)->toBe('Related Post') + ->and($definition->toField->entity_type)->toBe(Comment::class); + }); + + it('collapses to one field when the toggle makes the relationship symmetric', function (): void { + mountRelationshipField($this->postSection, targetEntityType: Post::class) + ->set('mountedActions.0.data.relationship.is_symmetric', true) + ->set('mountedActions.0.data.relationship.cardinality', RelationshipCardinality::ManyToMany->value) + ->assertSchemaComponentHidden('relationship.paired_field_name') + ->callMountedAction() + ->assertHasNoActionErrors(); + + $definition = CustomFieldRelationship::query()->sole(); + + expect($definition->is_symmetric)->toBeTrue() + ->and($definition->to_field_id)->toBe($definition->from_field_id) + ->and(CustomField::query()->count())->toBe(1); + }); + + it('reports a record configuration with nowhere to point', function (): void { + mountRelationshipField($this->postSection, targetEntityType: null) + ->set('mountedActions.0.data.relationship.cardinality', RelationshipCardinality::ManyToOne->value) + ->callMountedAction() + ->assertHasActionErrors(['relationship.target_entity_type' => 'required']); + + expect(CustomFieldRelationship::query()->count())->toBe(0); + }); + + it('asks for a cardinality before it writes a definition', function (): void { + mountRelationshipField($this->postSection) + ->set('mountedActions.0.data.relationship.cardinality') + ->callMountedAction() + ->assertHasActionErrors(['relationship.cardinality' => 'required']); + + expect(CustomFieldRelationship::query()->count())->toBe(0); + }); + + it('refuses a machine code another field on the entity already answers to', function (): void { + CustomField::factory()->ofType('text')->create([ + 'entity_type' => Post::class, + 'code' => 'related_comment', + ]); + + mountRelationshipField($this->postSection) + ->set('mountedActions.0.data.relationship.cardinality', RelationshipCardinality::ManyToOne->value) + ->callMountedAction() + ->assertHasActionErrors(['code' => 'unique']); + }); +}); + +describe('lifecycle', function (): void { + it('refuses to narrow the cardinality without the keep-first confirmation', function (): void { + $definition = manyToManyComments($this->postSection); + + livewire(ManageCustomField::class, ['field' => $definition->fromField]) + ->mountAction('edit') + ->set('mountedActions.0.data.relationship.cardinality', RelationshipCardinality::ManyToOne->value) + ->assertSchemaComponentVisible('relationship.keep_first') + ->callMountedAction() + ->assertHasActionErrors(['relationship.keep_first']); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::ManyToMany); + }); + + it('locks both ends once the definition exists', function (): void { + $definition = manyToManyComments($this->postSection); + + livewire(ManageCustomField::class, ['field' => $definition->fromField]) + ->mountAction('edit') + ->assertSchemaComponentHidden('relationship.is_symmetric') + ->assertSchemaComponentExists( + 'relationship.target_entity_type', + checkComponentUsing: fn (Select $component): bool => $component->isDisabled(), + ); + }); +}); + +describe('keyboard and disclosure', function (): void { + it('submits the field form from the keyboard', function (): void { + $component = livewire(ManageCustomFieldSection::class, [ + 'section' => $this->postSection, + 'entityType' => Post::class, + ])->mountAction('createField'); + + $attributes = $component->instance()->getMountedActions()[0]->getExtraModalWindowAttributes(); + + expect($attributes)->toHaveKeys(['x-on:keydown.meta.enter.prevent', 'x-on:keydown.ctrl.enter.prevent']) + ->and($attributes['x-on:keydown.meta.enter.prevent'])->toBe('$el.requestSubmit()'); + }); + + it('keeps the machine code behind an advanced disclosure', function (): void { + $component = livewire(ManageCustomFieldSection::class, [ + 'section' => $this->postSection, + 'entityType' => Post::class, + ])->mountAction('createField'); + + $livewire = $component->instance(); + $advanced = $livewire + ->getSchema($livewire->getMountedActionSchemaName()) + ?->getComponent(fn (mixed $component): bool => $component instanceof Section + && $component->getHeading() === 'Advanced', withHidden: true); + + expect($advanced)->not->toBeNull() + ->and($advanced->isCollapsed())->toBeTrue(); + + $component->assertSchemaComponentExists('code'); + }); +}); + +function manyToManyComments(CustomFieldSection $section): CustomFieldRelationship +{ + return app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_comment', + fromEntityType: Post::class, + toEntityType: Comment::class, + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Related Comment', sectionId: $section->getKey(), type: RelationshipFieldType::KEY), + )); +} diff --git a/tests/Feature/Relationships/RelationshipDefinitionModelTest.php b/tests/Feature/Relationships/RelationshipDefinitionModelTest.php new file mode 100644 index 00000000..ef1435f3 --- /dev/null +++ b/tests/Feature/Relationships/RelationshipDefinitionModelTest.php @@ -0,0 +1,88 @@ +create(); + + $from = CustomField::factory()->create([ + 'type' => 'record', + 'entity_type' => 'post', + 'custom_field_section_id' => $section->id, + ]); + + $to = CustomField::factory()->create([ + 'type' => 'record', + 'entity_type' => 'user', + 'custom_field_section_id' => $section->id, + ]); + + $definition = CustomFieldRelationship::factory()->create([ + 'code' => 'author_of', + 'from_entity_type' => 'post', + 'to_entity_type' => 'user', + 'cardinality' => RelationshipCardinality::ManyToOne, + 'from_field_id' => $from->id, + 'to_field_id' => $to->id, + ]); + + expect($definition->cardinality)->toBe(RelationshipCardinality::ManyToOne) + ->and($definition->directionFor($from))->toBe('from') + ->and($definition->directionFor($to))->toBe('to') + ->and($definition->isHeadless())->toBeFalse() + ->and($definition->fromField)->toBeSameModel($from) + ->and($definition->toField)->toBeSameModel($to) + ->and($from->relationshipDefinition()?->id)->toBe($definition->id); +}); + +it('supports headless definitions with no fields', function (): void { + $definition = CustomFieldRelationship::factory()->create([ + 'from_field_id' => null, + 'to_field_id' => null, + ]); + + expect($definition->isHeadless())->toBeTrue() + ->and($definition->is_symmetric)->toBeFalse(); +}); + +it('rejects directionFor on an unrelated field', function (): void { + $definition = CustomFieldRelationship::factory()->create(); + $stranger = CustomField::factory()->create(['type' => 'record']); + + $definition->directionFor($stranger); +})->throws(InvalidArgumentException::class); + +it('matches no definition for a field that has never been saved', function (): void { + $section = CustomFieldSection::factory()->create(); + + CustomFieldRelationship::factory()->create(['from_field_id' => null, 'to_field_id' => null]); + + $field = CustomField::factory()->make(['type' => 'record', 'custom_field_section_id' => $section->id]); + + expect($field->relationshipDefinition())->toBeNull(); + + $field->save(); + + $definition = CustomFieldRelationship::factory()->create(['to_field_id' => $field->id]); + + expect($field->relationshipDefinition()?->id)->toBe($definition->id); +}); + +it('rejects directionFor on a field that has never been saved', function (): void { + $definition = CustomFieldRelationship::factory()->create(['from_field_id' => null, 'to_field_id' => null]); + + $definition->directionFor(CustomField::factory()->make(['type' => 'record'])); +})->throws(InvalidArgumentException::class); + +it('resolves the definition model through the swap registry', function (): void { + expect(CustomFields::relationshipModel())->toBe(CustomFieldRelationship::class) + ->and(CustomFields::newRelationshipModel())->toBeInstanceOf(CustomFieldRelationship::class) + ->and(CustomFields::newRelationshipModel()->getTable()) + ->toBe(config('custom-fields.database.table_names.custom_field_relationships')); +}); diff --git a/tests/Feature/Relationships/RelationshipFieldTypeTest.php b/tests/Feature/Relationships/RelationshipFieldTypeTest.php new file mode 100644 index 00000000..3c2ff548 --- /dev/null +++ b/tests/Feature/Relationships/RelationshipFieldTypeTest.php @@ -0,0 +1,89 @@ +execute(new RelationshipDefinitionData( + code: 'authorship_'.$type, + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData( + name: 'Authors '.$type, + sectionId: sectionForEntity((new Post)->getMorphClass())->getKey(), + type: $type, + ), + )); +} + +/** + * @return array + */ +function activeLinkTargets(): array +{ + return CustomFieldLink::query()->active()->orderBy('sort_order')->pluck('to_entity_id')->all(); +} + +it('creates, reads and clears links for both linking field types', function (string $type): void { + $definition = linkingFieldOfType($type); + $field = $definition->fromField; + $user = User::factory()->create(); + + $post = Post::factory()->create(['custom_fields' => [$field->code => [$user->getKey()]]]); + + expect($field->type)->toBe($type) + ->and(activeLinkTargets())->toEqual([(string) $user->getKey()]) + ->and($post->fresh()->getCustomFieldValue($field))->toEqual([$user->getKey()]); + + $post->update(['custom_fields' => [$field->code => []]]); + + expect(activeLinkTargets())->toBe([]); +})->with([RecordFieldType::KEY, RelationshipFieldType::KEY]); + +it('registers both linking types with the pairing capability telling them apart', function (): void { + $record = CustomFieldsType::getFieldType(RecordFieldType::KEY); + $relationship = CustomFieldsType::getFieldType(RelationshipFieldType::KEY); + + expect($record?->requiresRelationship)->toBeTrue() + ->and($record?->supportsPairing)->toBeFalse() + ->and($relationship?->requiresRelationship)->toBeTrue() + ->and($relationship?->supportsPairing)->toBeTrue(); +}); + +it('lists both linking types in the type picker, each with its own description', function (): void { + $schema = Schema::make(livewire(ManageFieldsTable::class, ['entityType' => Post::class])->instance()) + ->statePath('data') + ->components([TypeField::make('type')]); + + $field = $schema->getComponent(fn (mixed $component): bool => $component instanceof TypeField, withHidden: true); + + expect($field)->toBeInstanceOf(TypeField::class); + + $choices = collect($field->getTypeChoices())->keyBy('key'); + + expect($choices->has(RecordFieldType::KEY))->toBeTrue() + ->and($choices->has(RelationshipFieldType::KEY))->toBeTrue() + ->and($choices[RecordFieldType::KEY]['description'])->toBe('A one-way link to records of another entity.') + ->and($choices[RelationshipFieldType::KEY]['description'])->toBe('A two-way link, with a matching field on the other entity.'); +}); + +it('refuses a slot rendered by a field type that stores no links', function (): void { + expect(fn (): CustomFieldRelationship => linkingFieldOfType('text')) + ->toThrow(InvalidArgumentException::class, 'cannot render the [text] field type'); +}); diff --git a/tests/Feature/Relationships/RelationshipMigrationsTest.php b/tests/Feature/Relationships/RelationshipMigrationsTest.php new file mode 100644 index 00000000..d77fb866 --- /dev/null +++ b/tests/Feature/Relationships/RelationshipMigrationsTest.php @@ -0,0 +1,93 @@ + require dirname(__DIR__, 3).'/database/migrations/create_relationship_definitions_table.php'; +$linksMigration = fn (): Migration => require dirname(__DIR__, 3).'/database/migrations/create_relationship_links_table.php'; + +it('points the partial index at the prefixed table', function () use ($linksMigration): void { + $connection = Schema::getConnection(); + $original = $connection->getTablePrefix(); + $connection->setTablePrefix('pfx_'); + + try { + $statement = collect(DB::pretend(function () use ($linksMigration): void { + $linksMigration()->up(); + })) + ->pluck('query') + ->first(fn (string $query): bool => str_contains($query, 'cf_links_active_edge_unique')); + } finally { + $connection->setTablePrefix($original); + } + + expect($statement)->toContain('pfx_custom_field_links'); +})->skip( + fn (): bool => ! in_array(DB::connection()->getDriverName(), ['pgsql', 'sqlite'], true), + 'The MySQL family has no partial index, so there is nothing to prefix.', +); + +it('creates neither table when relationships are disabled', function () use ($definitionsMigration, $linksMigration): void { + config()->set('custom-fields.features', FeatureConfigurator::configure() + ->disable(CustomFieldsFeature::SYSTEM_RELATIONSHIPS)); + config()->set('custom-fields.database.table_names.custom_field_relationships', 'probe_relationships'); + config()->set('custom-fields.database.table_names.custom_field_links', 'probe_links'); + + $definitionsMigration()->up(); + $linksMigration()->up(); + + expect(Schema::hasTable('probe_relationships'))->toBeFalse() + ->and(Schema::hasTable('probe_links'))->toBeFalse(); +}); + +it('scopes the definition code to the tenant when multi-tenancy is enabled', function () use ($definitionsMigration): void { + config()->set('custom-fields.features', FeatureConfigurator::configure()->enable( + CustomFieldsFeature::SYSTEM_RELATIONSHIPS, + CustomFieldsFeature::SYSTEM_MULTI_TENANCY, + )); + config()->set('custom-fields.database.table_names.custom_field_relationships', 'probe_relationships'); + + $definitionsMigration()->up(); + + $codeIndex = collect(Schema::getIndexes('probe_relationships')) + ->first(fn (array $index): bool => $index['unique'] && in_array('code', $index['columns'], true)); + + expect(Schema::hasColumn('probe_relationships', 'tenant_id'))->toBeTrue() + ->and($codeIndex['columns'])->toEqualCanonicalizing(['code', 'tenant_id']); +}); + +it('stamps the links table with a tenant key when multi-tenancy is enabled', function () use ($linksMigration): void { + config()->set('custom-fields.features', FeatureConfigurator::configure()->enable( + CustomFieldsFeature::SYSTEM_RELATIONSHIPS, + CustomFieldsFeature::SYSTEM_MULTI_TENANCY, + )); + config()->set('custom-fields.database.table_names.custom_field_links', 'probe_links'); + + $create = collect(DB::pretend(function () use ($linksMigration): void { + $linksMigration()->up(); + })) + ->pluck('query') + ->first(fn (string $query): bool => str_contains($query, 'create table')); + + expect($create)->toContain('probe_links') + ->toContain('tenant_id'); +}); + +it('keys the slot columns off the custom field model, not the package key type', function () use ($definitionsMigration): void { + config()->set('custom-fields.database.key_type', 'ulid'); + config()->set('custom-fields.database.table_names.custom_field_relationships', 'probe_relationships'); + + $definitionsMigration()->up(); + + $definitions = collect(Schema::getColumns('probe_relationships'))->keyBy('name'); + $customFieldKey = collect(Schema::getColumns('custom_fields'))->keyBy('name')->get('id'); + + expect($definitions->get('from_field_id')['type_name'])->toBe($customFieldKey['type_name']) + ->and($definitions->get('to_field_id')['type_name'])->toBe($customFieldKey['type_name']) + ->and($definitions->get('id')['type_name'])->not->toBe($customFieldKey['type_name']); +}); diff --git a/tests/Feature/Relationships/UpdateRelationshipDefinitionTest.php b/tests/Feature/Relationships/UpdateRelationshipDefinitionTest.php new file mode 100644 index 00000000..5f83fcdb --- /dev/null +++ b/tests/Feature/Relationships/UpdateRelationshipDefinitionTest.php @@ -0,0 +1,122 @@ +execute(new RelationshipDefinitionData( + code: 'reviewers', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new User)->getMorphClass(), + cardinality: $cardinality, + fromField: new FieldSlotData(name: 'Reviewers', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + )); +} + +it('leaves the edges alone when the cardinality does not change', function (): void { + $definition = reviewers(); + $reviewers = User::factory()->count(2)->create(); + + Post::factory()->create(['custom_fields' => [$definition->fromField->code => $reviewers->modelKeys()]]); + + app(UpdateRelationshipDefinition::class)->execute($definition, RelationshipCardinality::ManyToMany); + + expect(CustomFieldLink::query()->active()->count())->toBe(2); +}); + +it('closes nothing when the cardinality widens', function (): void { + $definition = reviewers(RelationshipCardinality::ManyToOne); + $reviewer = User::factory()->create(); + + Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$reviewer->getKey()]]]); + + app(UpdateRelationshipDefinition::class)->execute($definition, RelationshipCardinality::ManyToMany); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::ManyToMany) + ->and(CustomFieldLink::query()->active()->count())->toBe(1); +}); + +it('refuses to narrow an end without the keep-first confirmation', function (): void { + $definition = reviewers(); + $reviewers = User::factory()->count(2)->create(); + + Post::factory()->create(['custom_fields' => [$definition->fromField->code => $reviewers->modelKeys()]]); + + try { + app(UpdateRelationshipDefinition::class)->execute($definition, RelationshipCardinality::ManyToOne); + } catch (ValidationException) { + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::ManyToMany) + ->and(CustomFieldLink::query()->active()->count())->toBe(2); + + return; + } + + $this->fail('Narrowing was accepted without the keep-first confirmation.'); +}); + +it('keeps the first edge of each holder on the narrowed end', function (): void { + $definition = reviewers(); + $reviewers = User::factory()->count(3)->create(); + + $first = Post::factory()->create(['custom_fields' => [$definition->fromField->code => $reviewers->modelKeys()]]); + $second = Post::factory()->create(['custom_fields' => [$definition->fromField->code => [$reviewers[2]->getKey(), $reviewers[1]->getKey()]]]); + + app(UpdateRelationshipDefinition::class)->execute($definition, RelationshipCardinality::ManyToOne, keepFirst: true); + + expect($definition->refresh()->cardinality)->toBe(RelationshipCardinality::ManyToOne) + ->and($first->fresh()->getCustomFieldValue($definition->fromField))->toBe([$reviewers[0]->getKey()]) + ->and($second->fresh()->getCustomFieldValue($definition->fromField))->toBe([$reviewers[2]->getKey()]) + ->and(CustomFieldLink::query()->whereNotNull('active_until')->count())->toBe(3); +}); + +it('keeps one edge per record on either end of a symmetric definition', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_posts', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + isSymmetric: true, + fromField: new FieldSlotData(name: 'Related Posts', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + )); + + $posts = Post::factory()->count(3)->create(); + $posts[0]->update(['custom_fields' => [$definition->fromField->code => [$posts[1]->getKey(), $posts[2]->getKey()]]]); + + app(UpdateRelationshipDefinition::class)->execute($definition, RelationshipCardinality::OneToOne, keepFirst: true); + + expect(CustomFieldLink::query()->active()->count())->toBe(1) + ->and($posts[0]->fresh()->getCustomFieldValue($definition->fromField))->toBe([$posts[1]->getKey()]); +}); + +it('keeps a record on one end from closing its own edge on the other', function (): void { + $definition = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'reports_to', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Reports To', sectionId: sectionForEntity((new Post)->getMorphClass())->getKey()), + )); + + $code = $definition->fromField->code; + [$a, $b, $c] = Post::factory()->count(3)->create(); + + $a->update(['custom_fields' => [$code => [$b->getKey()]]]); + $c->update(['custom_fields' => [$code => [$a->getKey()]]]); + + app(UpdateRelationshipDefinition::class)->execute($definition, RelationshipCardinality::OneToOne, keepFirst: true); + + expect(CustomFieldLink::query()->active()->count())->toBe(2) + ->and($a->fresh()->getCustomFieldValue($definition->fromField))->toBe([$b->getKey()]) + ->and($c->fresh()->getCustomFieldValue($definition->fromField))->toBe([$a->getKey()]); +}); diff --git a/tests/Feature/RelaxCustomFieldsUniqueKeyMigrationTest.php b/tests/Feature/RelaxCustomFieldsUniqueKeyMigrationTest.php index 304b7968..13443b53 100644 --- a/tests/Feature/RelaxCustomFieldsUniqueKeyMigrationTest.php +++ b/tests/Feature/RelaxCustomFieldsUniqueKeyMigrationTest.php @@ -6,16 +6,12 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; -use Relaticle\CustomFields\Models\CustomField; -use Relaticle\CustomFields\Models\CustomFieldSection; -use Relaticle\CustomFields\Tests\Fixtures\Models\Post; /** * loadMigrationsFrom() (see tests/TestCase.php) already ran this migration's up() once * during suite bootstrap, so every test here starts from the wide (post-up) index state. - * RefreshDatabase wraps each test in a DB transaction, and SQLite's DDL is transactional, - * so schema changes made in one test — including index drops/adds — never leak into the - * next; each test is free to call up()/down() as many times as it needs. + * up() is idempotent regardless of starting state, so every test ends in the state it + * asserts on all three drivers whatever order the suite runs in. */ beforeEach(function (): void { $this->migration = require __DIR__.'/../../database/migrations/relax_custom_fields_unique_key.php'; @@ -27,31 +23,6 @@ function customFieldsIndexNames(string $table): Collection return collect(Schema::getIndexes($table))->pluck('name'); } -it('down() restores the narrow key and drops the wide one', function (): void { - expect(customFieldsIndexNames($this->table)) - ->toContain('cf_code_entity_section_unique') - ->not->toContain('custom_fields_code_entity_type_unique'); - - $this->migration->down(); - - $indexes = customFieldsIndexNames($this->table); - - expect($indexes) - ->toContain('custom_fields_code_entity_type_unique') - ->not->toContain('cf_code_entity_section_unique'); -}); - -it('up() restores the wide key after a down() round trip', function (): void { - $this->migration->down(); - $this->migration->up(); - - $indexes = customFieldsIndexNames($this->table); - - expect($indexes) - ->toContain('cf_code_entity_section_unique') - ->not->toContain('custom_fields_code_entity_type_unique'); -}); - it('up() is idempotent when the wide index already exists', function (): void { expect(fn () => $this->migration->up())->not->toThrow(Throwable::class); @@ -72,26 +43,15 @@ function customFieldsIndexNames(string $table): Collection expect(customFieldsIndexNames($this->table))->toContain('cf_code_entity_section_unique'); }); -it('down() aborts before dropping anything when rows share a code across sections', function (): void { - $sectionA = CustomFieldSection::factory()->create(['entity_type' => Post::class, 'code' => 'section_a']); - $sectionB = CustomFieldSection::factory()->create(['entity_type' => Post::class, 'code' => 'section_b']); - - CustomField::factory()->create([ - 'custom_field_section_id' => $sectionA->id, - 'entity_type' => Post::class, - 'code' => 'duplicate_code', - 'type' => 'text', - ]); +it('up() swaps the narrow key for the wide one when the narrow key is the one present', function (): void { + Schema::table($this->table, fn (Blueprint $table) => $table->dropUnique('cf_code_entity_section_unique')); + Schema::table($this->table, fn (Blueprint $table) => $table->unique(['code', 'entity_type'], 'custom_fields_code_entity_type_unique')); - CustomField::factory()->create([ - 'custom_field_section_id' => $sectionB->id, - 'entity_type' => Post::class, - 'code' => 'duplicate_code', - 'type' => 'text', - ]); + expect(customFieldsIndexNames($this->table)) + ->toContain('custom_fields_code_entity_type_unique') + ->not->toContain('cf_code_entity_section_unique'); - expect(fn () => $this->migration->down()) - ->toThrow(RuntimeException::class, 'duplicate_code'); + $this->migration->up(); $indexes = customFieldsIndexNames($this->table); @@ -100,33 +60,6 @@ function customFieldsIndexNames(string $table): Collection ->not->toContain('custom_fields_code_entity_type_unique'); }); -it('down() succeeds once the duplicate rows are resolved', function (): void { - $sectionA = CustomFieldSection::factory()->create(['entity_type' => Post::class, 'code' => 'section_a']); - $sectionB = CustomFieldSection::factory()->create(['entity_type' => Post::class, 'code' => 'section_b']); - - CustomField::factory()->create([ - 'custom_field_section_id' => $sectionA->id, - 'entity_type' => Post::class, - 'code' => 'duplicate_code', - 'type' => 'text', - ]); - - $duplicate = CustomField::factory()->create([ - 'custom_field_section_id' => $sectionB->id, - 'entity_type' => Post::class, - 'code' => 'duplicate_code', - 'type' => 'text', - ]); - - $duplicate->update(['code' => 'no_longer_duplicate']); - - expect(fn () => $this->migration->down())->not->toThrow(Throwable::class); - - expect(customFieldsIndexNames($this->table)) - ->toContain('custom_fields_code_entity_type_unique') - ->not->toContain('cf_code_entity_section_unique'); -}); - it('honors prefix_indexes and the connection table prefix when computing the drop-target index name', function (): void { config()->set('database.connections.prefixed_for_test', [ 'driver' => 'sqlite', diff --git a/tests/Feature/Rules/UniqueCustomFieldValueTest.php b/tests/Feature/Rules/UniqueCustomFieldValueTest.php index 0895fb21..e2d3ee7d 100644 --- a/tests/Feature/Rules/UniqueCustomFieldValueTest.php +++ b/tests/Feature/Rules/UniqueCustomFieldValueTest.php @@ -523,3 +523,30 @@ function (string $message) use (&$errors): void { expect($errors)->toBeEmpty(); }); }); + +describe('Invalid entity type handling', function (): void { + it('throws a clear exception instead of a fatal error for an unresolvable entity type', function (): void { + $section = CustomFieldSection::factory() + ->forEntityType(Post::class) + ->create(['active' => true]); + + $field = CustomField::factory()->create([ + 'custom_field_section_id' => $section->getKey(), + 'entity_type' => 'Bogus\\Missing\\Entity', + 'code' => 'bogus_code', + 'name' => 'Bogus Code', + 'type' => 'text', + 'settings' => new CustomFieldSettingsData( + unique_per_entity_type: true, + ), + ]); + + $rule = new UniqueCustomFieldValue($field); + + expect(fn () => $rule->validate( + 'custom_fields.bogus_code', + 'some-value', + function (string $message): void {} + ))->toThrow(RuntimeException::class); + }); +}); diff --git a/tests/Feature/SectionModalWidthTest.php b/tests/Feature/SectionModalWidthTest.php index 2a2b9acb..793d8582 100644 --- a/tests/Feature/SectionModalWidthTest.php +++ b/tests/Feature/SectionModalWidthTest.php @@ -18,6 +18,7 @@ it('keeps the narrower 2xl section modal when conditional visibility is disabled', function (): void { config()->set('custom-fields.features', FeatureConfigurator::configure() ->enable(CustomFieldsFeature::SYSTEM_SECTIONS) + ->disable(CustomFieldsFeature::SECTION_CONDITIONAL_VISIBILITY) ); expect(CustomFieldsPlugin::make()->getSectionModalWidth())->toBe(Width::TwoExtraLarge); diff --git a/tests/Feature/SectionWidthTest.php b/tests/Feature/SectionWidthTest.php index cdd51c6e..8bcbc287 100644 --- a/tests/Feature/SectionWidthTest.php +++ b/tests/Feature/SectionWidthTest.php @@ -2,7 +2,6 @@ declare(strict_types=1); -use Relaticle\CustomFields\Contracts\CustomsFieldsMigrators; use Relaticle\CustomFields\Data\CustomFieldData; use Relaticle\CustomFields\Data\CustomFieldSectionData; use Relaticle\CustomFields\Enums\CustomFieldSectionType; @@ -12,6 +11,7 @@ use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Filament\Integration\Factories\SectionComponentFactory; use Relaticle\CustomFields\Filament\Integration\Factories\SectionInfolistsFactory; +use Relaticle\CustomFields\Filament\Integration\Migrations\CustomFieldsMigrator; use Relaticle\CustomFields\Filament\Management\Pages\CustomFieldsManagementPage; use Relaticle\CustomFields\Livewire\ManageCustomFieldSection; use Relaticle\CustomFields\Models\CustomFieldSection; @@ -37,8 +37,10 @@ ]); }); -it('has UI_SECTION_WIDTH_CONTROL disabled by default', function (): void { - expect(FeatureManager::isEnabled(CustomFieldsFeature::UI_SECTION_WIDTH_CONTROL))->toBeFalse(); +it('ships UI_SECTION_WIDTH_CONTROL enabled', function (): void { + config(['custom-fields.features' => shippedFeatureConfigurator()]); + + expect(FeatureManager::isEnabled(CustomFieldsFeature::UI_SECTION_WIDTH_CONTROL))->toBeTrue(); }); it('renders a fractional column span when the flag is on and width is non-100', function (): void { @@ -69,7 +71,8 @@ it('ignores section width when the flag is off', function (): void { config(['custom-fields.features' => FeatureConfigurator::configure() - ->enable(CustomFieldsFeature::SYSTEM_SECTIONS)]); + ->enable(CustomFieldsFeature::SYSTEM_SECTIONS) + ->disable(CustomFieldsFeature::UI_SECTION_WIDTH_CONTROL)]); $section = CustomFieldSection::factory() ->width(CustomFieldWidth::_50) @@ -110,7 +113,8 @@ it('ignores section width on the infolist path when the flag is off', function (): void { config(['custom-fields.features' => FeatureConfigurator::configure() - ->enable(CustomFieldsFeature::SYSTEM_SECTIONS)]); + ->enable(CustomFieldsFeature::SYSTEM_SECTIONS) + ->disable(CustomFieldsFeature::UI_SECTION_WIDTH_CONTROL)]); $section = CustomFieldSection::factory() ->width(CustomFieldWidth::_50) @@ -147,10 +151,12 @@ }); it('does not persist section width from the form when the flag is off', function (): void { - config(['custom-fields.features' => FeatureConfigurator::configure()->enable( - CustomFieldsFeature::SYSTEM_SECTIONS, - CustomFieldsFeature::SYSTEM_MANAGEMENT_INTERFACE, - )]); + config(['custom-fields.features' => FeatureConfigurator::configure() + ->enable( + CustomFieldsFeature::SYSTEM_SECTIONS, + CustomFieldsFeature::SYSTEM_MANAGEMENT_INTERFACE, + ) + ->disable(CustomFieldsFeature::UI_SECTION_WIDTH_CONTROL)]); $this->actingAs(User::factory()->create()); @@ -265,7 +271,7 @@ config(['custom-fields.features' => FeatureConfigurator::configure() ->enable(CustomFieldsFeature::SYSTEM_SECTIONS)]); - app(CustomsFieldsMigrators::class)->new( + app(CustomFieldsMigrator::class)->new( model: Post::class, fieldData: new CustomFieldData( name: 'Function Info', diff --git a/tests/Feature/Services/ModelAttributeDiscoveryServiceTest.php b/tests/Feature/Services/ModelAttributeDiscoveryServiceTest.php index 29f66b9e..1d186792 100644 --- a/tests/Feature/Services/ModelAttributeDiscoveryServiceTest.php +++ b/tests/Feature/Services/ModelAttributeDiscoveryServiceTest.php @@ -2,9 +2,14 @@ declare(strict_types=1); +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; use Relaticle\CustomFields\Enums\FieldDataType; use Relaticle\CustomFields\Services\ModelAttributeDiscoveryService; +use Relaticle\CustomFields\Tests\Fixtures\Models\JsonbColumnModel; use Relaticle\CustomFields\Tests\Fixtures\Models\Post; +use Relaticle\CustomFields\Tests\Fixtures\Models\SecondConnectionModel; beforeEach(function (): void { ModelAttributeDiscoveryService::clearCache(); @@ -39,6 +44,25 @@ ->and($attributes->has('json_array_of_objects'))->toBeFalse(); }); +it('excludes jsonb columns on postgres', function (): void { + if (DB::connection()->getDriverName() !== 'pgsql') { + $this->markTestSkipped('jsonb is a Postgres-only column type.'); + } + + Schema::create('jsonb_column_models', function (Blueprint $table): void { + $table->id(); + $table->string('title'); + $table->jsonb('payload'); + }); + + $attributes = $this->service->getAttributes(JsonbColumnModel::class); + + expect($attributes->has('title'))->toBeTrue() + ->and($attributes->has('payload'))->toBeFalse(); + + Schema::dropIfExists('jsonb_column_models'); +}); + it('maps column types to correct FieldDataType', function (): void { $attributes = $this->service->getAttributes(Post::class); @@ -89,3 +113,23 @@ expect($attributes)->toBeEmpty(); }); + +it("discovers columns from the model's own connection instead of the default connection", function (): void { + config()->set('database.connections.second', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + + app('db')->connection('second')->getSchemaBuilder()->create( + 'second_connection_models', + function (Blueprint $table): void { + $table->id(); + $table->string('only_on_second_connection'); + } + ); + + $attributes = $this->service->getAttributes(SecondConnectionModel::class); + + expect($attributes->has('only_on_second_connection'))->toBeTrue(); +}); diff --git a/tests/Feature/Services/ValueResolver/LookupResolverBatchingTest.php b/tests/Feature/Services/ValueResolver/LookupResolverBatchingTest.php index a633bc01..0d77d593 100644 --- a/tests/Feature/Services/ValueResolver/LookupResolverBatchingTest.php +++ b/tests/Feature/Services/ValueResolver/LookupResolverBatchingTest.php @@ -4,10 +4,12 @@ use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; -use Relaticle\CustomFields\Data\CustomFieldSettingsData; -use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Data\FieldSlotData; +use Relaticle\CustomFields\Data\RelationshipDefinitionData; +use Relaticle\CustomFields\Enums\RelationshipCardinality; use Relaticle\CustomFields\Models\CustomFieldSection; use Relaticle\CustomFields\Models\CustomFieldValue; +use Relaticle\CustomFields\Services\Relationships\CreateRelationshipDefinition; use Relaticle\CustomFields\Services\ValueResolver\LookupCache; use Relaticle\CustomFields\Services\ValueResolver\LookupResolver; use Relaticle\CustomFields\Tests\Fixtures\Models\Post; @@ -21,15 +23,13 @@ ->forEntityType(Post::class) ->create(['active' => true]); - $this->field = CustomField::factory()->create([ - 'custom_field_section_id' => $section->getKey(), - 'entity_type' => Post::class, - 'code' => 'parent_post', - 'name' => 'Parent Post', - 'type' => 'select', - 'lookup_type' => Post::class, - 'settings' => new CustomFieldSettingsData, - ]); + $this->field = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'parent_post', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToOne, + fromField: new FieldSlotData(name: 'Parent Post', sectionId: $section->getKey()), + ))->fromField; }); it('fires one query on first call and zero on second call for the same ids', function (): void { @@ -130,12 +130,7 @@ $hosts = Post::factory()->count(3)->create(); foreach ($hosts as $index => $host) { - CustomFieldValue::factory()->create([ - 'custom_field_id' => $this->field->getKey(), - 'entity_type' => Post::class, - 'entity_id' => $host->getKey(), - 'integer_value' => $targets[$index]->getKey(), - ]); + $host->update(['custom_fields' => [$this->field->code => [$targets[$index]->getKey()]]]); } app(LookupCache::class)->flush(); @@ -154,7 +149,7 @@ $titles = $loaded->map(function (Post $host) use ($resolver): string { $value = $host->getCustomFieldValue($this->field); - return $resolver->resolveLookupValues([$value], $this->field)->first() ?? ''; + return $resolver->resolveLookupValues($value, $this->field)->first() ?? ''; }); $postQueries = count(array_filter( @@ -178,15 +173,13 @@ ->forEntityType(Post::class) ->create(['active' => true]); - $multiField = CustomField::factory()->create([ - 'custom_field_section_id' => $section->getKey(), - 'entity_type' => Post::class, - 'code' => 'related_posts', - 'name' => 'Related Posts', - 'type' => 'multi-select', - 'lookup_type' => Post::class, - 'settings' => new CustomFieldSettingsData(allow_multiple: true), - ]); + $multiField = app(CreateRelationshipDefinition::class)->execute(new RelationshipDefinitionData( + code: 'related_posts', + fromEntityType: (new Post)->getMorphClass(), + toEntityType: (new Post)->getMorphClass(), + cardinality: RelationshipCardinality::ManyToMany, + fromField: new FieldSlotData(name: 'Related Posts', sectionId: $section->getKey()), + ))->fromField; $targets = Post::factory()->count(3)->create(); diff --git a/tests/Feature/StatusFieldTypeTest.php b/tests/Feature/StatusFieldTypeTest.php new file mode 100644 index 00000000..60dc6f2a --- /dev/null +++ b/tests/Feature/StatusFieldTypeTest.php @@ -0,0 +1,109 @@ +actingAs(User::factory()->create()); +}); + +function stageFieldOfType(string $type): CustomField +{ + livewire(ManageCustomFieldSection::class, [ + 'section' => sectionForEntity(Post::class), + 'entityType' => Post::class, + ]) + ->callAction('createField', [ + 'name' => 'Stage', + 'code' => 'stage', + 'type' => $type, + 'entity_type' => Post::class, + 'options' => [ + ['name' => 'Discovery'], + ['name' => 'Closed Won'], + ], + ]) + ->assertHasNoActionErrors(); + + return CustomField::query()->withoutGlobalScopes()->where('code', 'stage')->firstOrFail(); +} + +it('registers status as a single-choice type that carries option categories', function (): void { + $status = CustomFieldsType::getFieldType(StatusFieldType::KEY); + $select = CustomFieldsType::getFieldType('select'); + + expect($status?->dataType)->toBe(FieldDataType::SINGLE_CHOICE) + ->and($status?->carriesOptionCategories)->toBeTrue() + ->and($select?->carriesOptionCategories)->toBeFalse() + ->and($status?->encryptable)->toBeFalse() + ->and($select?->encryptable)->toBeTrue() + ->and($status?->searchable)->toBe($select?->searchable) + ->and($status?->sortable)->toBe($select?->sortable) + ->and($status?->filterable)->toBe($select?->filterable) + ->and($status?->tableColumn)->toBe($select?->tableColumn) + ->and($status?->tableFilter)->toBe($select?->tableFilter) + ->and($status?->formComponent)->toBe($select?->formComponent) + ->and($status?->infolistEntry)->toBe($select?->infolistEntry); +}); + +it('creates the field, holds a value, filters and sorts by it', function (string $type): void { + $field = stageFieldOfType($type); + + [$discovery, $closedWon] = $field->options()->orderBy('sort_order')->get()->all(); + + $early = Post::factory()->create(); + $won = Post::factory()->create(); + + $early->saveCustomFieldValue($field, (string) $discovery->getKey()); + $won->saveCustomFieldValue($field, (string) $closedWon->getKey()); + + expect($field->type)->toBe($type) + ->and($early->fresh()->getCustomFieldValue($field))->toEqual($discovery->getKey()); + + livewire(ListPosts::class) + ->assertTableColumnExists('custom_fields.stage') + ->assertCanRenderTableColumn('custom_fields.stage') + ->assertTableColumnFormattedStateSet('custom_fields.stage', 'Closed Won', $won) + ->filterTable('custom_fields.stage', [$closedWon->getKey()]) + ->assertCanSeeTableRecords([$won]) + ->assertCanNotSeeTableRecords([$early]) + ->resetTableFilters() + ->sortTable('custom_fields.stage', 'asc') + ->assertCanSeeTableRecords([$early, $won], inOrder: true) + ->sortTable('custom_fields.stage', 'desc') + ->assertCanSeeTableRecords([$won, $early], inOrder: true); +})->with(['select', StatusFieldType::KEY]); + +it('lists status in the type picker with its own description, in both flavors', function (string $flavor): void { + config()->set('custom-fields.ui.flavor', $flavor); + + $schema = Schema::make(livewire(ManageFieldsTable::class, ['entityType' => Post::class])->instance()) + ->statePath('data') + ->components([TypeField::make('type')]); + + $field = $schema->getComponent(fn (mixed $component): bool => $component instanceof TypeField, withHidden: true); + + expect($field)->toBeInstanceOf(TypeField::class); + + $choices = collect($field->getTypeChoices())->keyBy('key'); + + expect($choices->has(StatusFieldType::KEY))->toBeTrue() + ->and($choices[StatusFieldType::KEY]['label'])->toBe('Status') + ->and($choices[StatusFieldType::KEY]['description']) + ->toBe('One choice from a list of workflow states you define.') + ->and($choices['select']['description'])->toBe('One choice from a list you define.'); + + expect(array_keys($field->getEnabledOptions()))->toContain(StatusFieldType::KEY) + ->and($field->getEnabledOptions()[StatusFieldType::KEY])->toContain('Status'); +})->with(['polished', 'native']); diff --git a/tests/Feature/Support/KeyTypeTest.php b/tests/Feature/Support/KeyTypeTest.php new file mode 100644 index 00000000..e9531301 --- /dev/null +++ b/tests/Feature/Support/KeyTypeTest.php @@ -0,0 +1,54 @@ +set('custom-fields.database.key_type', $keyType); + + $columns = []; + + DB::pretend(function () use (&$columns): void { + Schema::create('key_type_probe', function (Blueprint $table) use (&$columns): void { + KeyType::primary($table); + KeyType::foreign($table, 'owner_id'); + KeyType::morphs($table, 'thing'); + KeyType::morphs($table, 'actor', nullable: true); + + foreach ($table->getColumns() as $column) { + $columns[(string) $column->get('name')] = $column; + } + }); + }); + + return $columns; +}; + +it('shapes key columns from the configured key type', function (string $keyType, string $idType) use ($probe): void { + $columns = $probe($keyType); + + expect($columns['id']->get('type'))->toBe($idType) + ->and($columns['owner_id']->get('type'))->toBe($idType) + ->and($columns['thing_id']->get('type'))->toBe($idType) + ->and($columns['actor_id']->get('type'))->toBe($idType) + ->and($columns['thing_type']->get('type'))->toBe('string'); +})->with([ + 'bigint' => ['bigint', 'bigInteger'], + 'ulid' => ['ulid', 'char'], + 'uuid' => ['uuid', 'uuid'], +]); + +it('makes only the nullable morph nullable', function () use ($probe): void { + $columns = $probe('ulid'); + + expect($columns['thing_id']->get('nullable'))->toBeFalsy() + ->and($columns['actor_id']->get('nullable'))->toBeTrue(); +}); + +it('rejects an unsupported key type', function () use ($probe): void { + $probe('snowflake'); +})->throws(InvalidArgumentException::class); diff --git a/tests/Feature/Support/ViewFlavorTest.php b/tests/Feature/Support/ViewFlavorTest.php new file mode 100644 index 00000000..26fcf70e --- /dev/null +++ b/tests/Feature/Support/ViewFlavorTest.php @@ -0,0 +1,148 @@ +toBe([ + 'relationship-configurator', + 'record-chips', + 'record-picker', + 'type-picker', + 'attribute-table', + ]); + }); + + it('resolves a surface to its polished view under the polished flavor', function (string $key): void { + config()->set('custom-fields.ui.flavor', 'polished'); + + $surface = UiSurface::from($key); + + expect(ViewFlavor::flavor($surface))->toBe(UiFlavor::Polished) + ->and(ViewFlavor::view($surface))->toBe('custom-fields::flavors.polished.'.$key); + })->with([ + 'relationship-configurator', + 'record-chips', + 'record-picker', + 'type-picker', + 'attribute-table', + ]); + + it('falls back to polished when the config predates the flavor registry', function (): void { + config()->set('custom-fields.ui', []); + + expect(ViewFlavor::flavor(UiSurface::AttributeTable))->toBe(UiFlavor::Polished); + }); + + it('drops every surface to the stock view under the global native flavor', function (): void { + config()->set('custom-fields.ui.flavor', 'native'); + + foreach (UiSurface::cases() as $surface) { + expect(ViewFlavor::flavor($surface))->toBe(UiFlavor::Native) + ->and(ViewFlavor::view($surface))->toBeNull(); + } + }); + + it('lets a per-surface override beat the global flavor in both directions', function (): void { + config()->set('custom-fields.ui.flavor', 'native'); + config()->set('custom-fields.ui.flavor_overrides', ['attribute-table' => 'polished']); + + expect(ViewFlavor::view(UiSurface::AttributeTable))->toBe('custom-fields::flavors.polished.attribute-table') + ->and(ViewFlavor::view(UiSurface::RecordChips))->toBeNull(); + + config()->set('custom-fields.ui.flavor', 'polished'); + config()->set('custom-fields.ui.flavor_overrides', ['record-chips' => 'native']); + + expect(ViewFlavor::view(UiSurface::RecordChips))->toBeNull() + ->and(ViewFlavor::view(UiSurface::AttributeTable))->toBe('custom-fields::flavors.polished.attribute-table'); + }); + + it('rejects an unknown global flavor', function (): void { + config()->set('custom-fields.ui.flavor', 'fancy'); + + ViewFlavor::view(UiSurface::AttributeTable); + })->throws(InvalidArgumentException::class, 'Unknown custom-fields UI flavor [fancy]'); + + it('validates the global flavor even when an override covers the surface', function (): void { + config()->set('custom-fields.ui.flavor', 'fancy'); + config()->set('custom-fields.ui.flavor_overrides', ['attribute-table' => 'polished']); + + ViewFlavor::view(UiSurface::AttributeTable); + })->throws(InvalidArgumentException::class, 'Unknown custom-fields UI flavor [fancy]'); + + it('rejects a bad flavor when the package boots, before any surface renders', function (): void { + config()->set('custom-fields.ui.flavor', 'fancy'); + (function (): void { + $this->isRunningInConsole = false; + })->call(app()); + + app()->register(CustomFieldsServiceProvider::class, force: true); + })->throws(InvalidArgumentException::class, 'Unknown custom-fields UI flavor [fancy]'); + + it('reports a bad flavor in the console so config:clear can recover from a cached one', function (): void { + Exceptions::fake(); + config()->set('custom-fields.ui.flavor', 'fancy'); + + app()->register(CustomFieldsServiceProvider::class, force: true); + + Exceptions::assertReported(fn (InvalidArgumentException $exception): bool => str_contains( + $exception->getMessage(), + 'Unknown custom-fields UI flavor [fancy]', + )); + }); + + it('rejects an unknown flavor in the override map', function (): void { + config()->set('custom-fields.ui.flavor_overrides', ['attribute-table' => 'fancy']); + + ViewFlavor::view(UiSurface::AttributeTable); + })->throws(InvalidArgumentException::class, 'Unknown custom-fields UI flavor [fancy]'); + + it('rejects a surface key that does not fork', function (): void { + config()->set('custom-fields.ui.flavor_overrides', ['field-form' => 'native']); + + ViewFlavor::view(UiSurface::AttributeTable); + })->throws(InvalidArgumentException::class, 'Unknown custom-fields UI surface [field-form]'); + + it('rejects an override map that is not a map', function (): void { + config()->set('custom-fields.ui.flavor_overrides', 'native'); + + ViewFlavor::view(UiSurface::AttributeTable); + })->throws(InvalidArgumentException::class, 'must be an array of surface keys to flavors'); +}); + +describe('attribute table rendering', function (): void { + beforeEach(function (): void { + $this->actingAs(User::factory()->create()); + + CustomField::factory()->ofType('text')->create([ + 'entity_type' => Post::class, + 'name' => 'Flavor smoke field', + ]); + }); + + it('renders the attribute table through the polished view', function (): void { + config()->set('custom-fields.ui.flavor', 'polished'); + + livewire(ManageFieldsTable::class, ['entityType' => Post::class]) + ->assertSee('Flavor smoke field') + ->assertSeeHtml('data-flavor="polished"'); + }); + + it('renders the attribute table through the stock view in the native flavor', function (): void { + config()->set('custom-fields.ui.flavor', 'native'); + + livewire(ManageFieldsTable::class, ['entityType' => Post::class]) + ->assertSee('Flavor smoke field') + ->assertDontSeeHtml('data-flavor='); + }); +}); diff --git a/tests/Feature/Translations/EnumLabelsRouteToTranslatorTest.php b/tests/Feature/Translations/EnumLabelsRouteToTranslatorTest.php index 97b433c5..50bf4da2 100644 --- a/tests/Feature/Translations/EnumLabelsRouteToTranslatorTest.php +++ b/tests/Feature/Translations/EnumLabelsRouteToTranslatorTest.php @@ -14,6 +14,8 @@ use Relaticle\CustomFields\Enums\DateUnit; use Relaticle\CustomFields\Enums\DescriptionPosition; use Relaticle\CustomFields\Enums\EntityFeature; +use Relaticle\CustomFields\Enums\OptionCategory; +use Relaticle\CustomFields\Enums\RelationshipCardinality; use Relaticle\CustomFields\Enums\VisibilityLogic; use Relaticle\CustomFields\Enums\VisibilityMode; use Relaticle\CustomFields\Enums\VisibilityOperator; @@ -77,6 +79,16 @@ [DescriptionPosition::class, 'below', 'enums.description_position.below'], [DescriptionPosition::class, 'above', 'enums.description_position.above'], + + [OptionCategory::class, 'unstarted', 'enums.option_category.unstarted'], + [OptionCategory::class, 'started', 'enums.option_category.started'], + [OptionCategory::class, 'completed', 'enums.option_category.completed'], + [OptionCategory::class, 'cancelled', 'enums.option_category.cancelled'], + + [RelationshipCardinality::class, 'one_to_one', 'enums.relationship_cardinality.one_to_one'], + [RelationshipCardinality::class, 'one_to_many', 'enums.relationship_cardinality.one_to_many'], + [RelationshipCardinality::class, 'many_to_one', 'enums.relationship_cardinality.many_to_one'], + [RelationshipCardinality::class, 'many_to_many', 'enums.relationship_cardinality.many_to_many'], ]); it('routes EntityFeature getDescription through translator', function (string $caseValue, string $key): void { diff --git a/tests/Feature/Translations/LanguageKeysTest.php b/tests/Feature/Translations/LanguageKeysTest.php index 468d1bce..ee6cdd55 100644 --- a/tests/Feature/Translations/LanguageKeysTest.php +++ b/tests/Feature/Translations/LanguageKeysTest.php @@ -53,6 +53,14 @@ 'enums.visibility_operator.is_not_empty', 'enums.description_position.below', 'enums.description_position.above', + 'enums.option_category.unstarted', + 'enums.option_category.started', + 'enums.option_category.completed', + 'enums.option_category.cancelled', + 'enums.relationship_cardinality.one_to_one', + 'enums.relationship_cardinality.one_to_many', + 'enums.relationship_cardinality.many_to_one', + 'enums.relationship_cardinality.many_to_many', // visibility.* 'visibility.heading', @@ -72,6 +80,10 @@ 'date_constraint.reference_field', 'date_constraint.date', + // field.form.options.* + 'field.form.options.category', + 'field.form.options.category_placeholder', + // field.actions.* (ManageCustomField row actions) 'field.actions.activate', 'field.actions.deactivate', diff --git a/tests/Feature/Translations/SourceUsesTranslationKeysTest.php b/tests/Feature/Translations/SourceUsesTranslationKeysTest.php index d3b1b50d..27bfa51c 100644 --- a/tests/Feature/Translations/SourceUsesTranslationKeysTest.php +++ b/tests/Feature/Translations/SourceUsesTranslationKeysTest.php @@ -32,6 +32,21 @@ 'visibility.mode', 'visibility.logic', 'visibility.conditions', + ], + ], + 'ConditionRow' => [ + 'relativePath' => 'Filament/Management/Forms/Components/Visibility/ConditionRow.php', + 'forbidden' => [ + "->label('Condition VisibilityLogic')", + "->label('VisibilityOperator')", + "->label('Visibility')", + "->label('Conditions')", + "->label('Source')", + "->label('Field')", + "->label('Value')", + "Fieldset::make('Conditional Visibility')", + ], + 'required' => [ 'visibility.source', 'visibility.field', 'visibility.operator', diff --git a/tests/Feature/VisibilityComponentSourcesTest.php b/tests/Feature/VisibilityComponentSourcesTest.php index ea6907a5..59fe3ebb 100644 --- a/tests/Feature/VisibilityComponentSourcesTest.php +++ b/tests/Feature/VisibilityComponentSourcesTest.php @@ -10,6 +10,7 @@ use Relaticle\CustomFields\Enums\VisibilityOperator; use Relaticle\CustomFields\Facades\CustomFieldsType; use Relaticle\CustomFields\FeatureSystem\FeatureConfigurator; +use Relaticle\CustomFields\Filament\Management\Forms\Components\Visibility\ConditionOptions; use Relaticle\CustomFields\Filament\Management\Forms\Components\VisibilityComponent; use Relaticle\CustomFields\Support\RelationConditionConfig; use Relaticle\CustomFields\Tests\Fixtures\Models\Comment; @@ -72,13 +73,6 @@ describe('custom-field fallback operator set excludes relation-only operators', function (): void { it('does not include IS_IN or IS_NOT_IN when no field type data is available (custom-field source with blank field_code)', function (): void { - // VisibilityComponent::getCompatibleOperators() is private; we reach it via reflection - // to verify the fallback branch (no $fieldData) excludes relation-only operators. - $component = new VisibilityComponent; - - $method = new ReflectionMethod($component, 'getCompatibleOperators'); - $method->setAccessible(true); - // Build a minimal Get stub that returns null/blank for all keys (simulates blank field_code, // CustomField source), forcing $fieldData to be null so the fallback branch executes. $get = new class extends Get @@ -94,7 +88,7 @@ public function __invoke(string|Component $path = '', bool $isAbsolute = false): } }; - $operators = $method->invoke($component, $get); + $operators = (new ConditionOptions)->getCompatibleOperators($get); expect(array_keys($operators)) ->not->toContain(VisibilityOperator::IS_IN->value, 'IS_IN must be excluded from the custom-field fallback operator list') @@ -116,8 +110,8 @@ public function __invoke(string|Component $path = '', bool $isAbsolute = false): // Comment is registered with conditionRelations in the test harness (post.tagModels => ...). $component = VisibilityComponent::makeForSection(Comment::class); - $method = new ReflectionMethod($component, 'getAvailableSourceOptions'); - $method->setAccessible(true); + $property = new ReflectionProperty($component, 'conditionOptions'); + $property->setAccessible(true); $get = new class extends Get { @@ -132,7 +126,7 @@ public function __invoke(string|Component $path = '', bool $isAbsolute = false): } }; - $options = $method->invoke($component, $get); + $options = $property->getValue($component)->getAvailableSourceOptions($get); expect(array_keys($options)) ->toContain(ConditionSource::RelationAttribute->value) diff --git a/tests/Fixtures/FieldTypes/TernaryToggleFieldType.php b/tests/Fixtures/FieldTypes/TernaryToggleFieldType.php new file mode 100644 index 00000000..a0074818 --- /dev/null +++ b/tests/Fixtures/FieldTypes/TernaryToggleFieldType.php @@ -0,0 +1,33 @@ +key('ternary-toggle') + ->label('Ternary Toggle') + ->icon('mdi-toggle-switch') + ->formComponent(ToggleComponent::class) + ->tableColumn(IconColumn::class) + ->tableFilter(TernaryFilter::class) + ->infolistEntry(BooleanEntry::class) + ->filterable() + ->priority(900); + } +} diff --git a/tests/Fixtures/Imports/PostImporter.php b/tests/Fixtures/Imports/PostImporter.php index 308956c8..6a3e7d2e 100644 --- a/tests/Fixtures/Imports/PostImporter.php +++ b/tests/Fixtures/Imports/PostImporter.php @@ -32,10 +32,14 @@ public static function getColumns(): array ]; } + /** + * An upsert on the title, which is what a host importing a spreadsheet twice has: the + * second pass updates the rows the first one created. + */ public function resolveRecord(): ?Model { - $post = new Post; - $post->author_id = $this->import->user_id; + $post = Post::query()->where('title', $this->data['title'] ?? null)->first() ?? new Post; + $post->author_id ??= $this->import->user_id; return $post; } diff --git a/tests/Fixtures/Livewire/ThroughTable.php b/tests/Fixtures/Livewire/ThroughTable.php new file mode 100644 index 00000000..c761b458 --- /dev/null +++ b/tests/Fixtures/Livewire/ThroughTable.php @@ -0,0 +1,45 @@ +file(__DIR__.'/through-table.blade.php'); + } +} diff --git a/tests/Fixtures/Livewire/through-table.blade.php b/tests/Fixtures/Livewire/through-table.blade.php new file mode 100644 index 00000000..c3fedc4a --- /dev/null +++ b/tests/Fixtures/Livewire/through-table.blade.php @@ -0,0 +1,3 @@ +
+ {{ $this->table }} +
diff --git a/tests/Fixtures/Models/Comment.php b/tests/Fixtures/Models/Comment.php index a6e5fb1e..560ad1ea 100644 --- a/tests/Fixtures/Models/Comment.php +++ b/tests/Fixtures/Models/Comment.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\MorphTo; use Relaticle\CustomFields\Models\Concerns\UsesCustomFields; use Relaticle\CustomFields\Models\Contracts\HasCustomFields; use Relaticle\CustomFields\Tests\Database\Factories\CommentFactory; @@ -23,6 +24,11 @@ public function post(): BelongsTo return $this->belongsTo(Post::class); } + public function commentable(): MorphTo + { + return $this->morphTo(); + } + protected static function newFactory() { return CommentFactory::new(); diff --git a/tests/Fixtures/Models/JsonbColumnModel.php b/tests/Fixtures/Models/JsonbColumnModel.php new file mode 100644 index 00000000..ea681e8c --- /dev/null +++ b/tests/Fixtures/Models/JsonbColumnModel.php @@ -0,0 +1,14 @@ +belongsToMany(Tag::class, 'post_tag'); } + public function comments(): HasMany + { + return $this->hasMany(Comment::class); + } + + public function featuredComment(): MorphOne + { + return $this->morphOne(Comment::class, 'commentable'); + } + protected static function newFactory() { return PostFactory::new(); diff --git a/tests/Fixtures/Models/SecondConnectionModel.php b/tests/Fixtures/Models/SecondConnectionModel.php new file mode 100644 index 00000000..a9e5ec8e --- /dev/null +++ b/tests/Fixtures/Models/SecondConnectionModel.php @@ -0,0 +1,16 @@ +hasMany(Post::class, 'author_id'); } - public function teams(): BelongsToMany + public function post(): HasOne { - return $this->belongsToMany(Team::class); + return $this->hasOne(Post::class, 'author_id'); + } + + public function publishedPost(): HasOne + { + return $this->hasOne(Post::class, 'author_id')->where('is_published', true); } protected static function newFactory() @@ -57,6 +61,6 @@ public function canAccessTenant(Model $tenant): bool public function getTenants(Panel $panel): array|Collection { - return Team::all(); + return collect(); } } diff --git a/tests/Fixtures/Providers/AdminPanelProvider.php b/tests/Fixtures/Providers/AdminPanelProvider.php index 9fd44cc9..73d9f78d 100644 --- a/tests/Fixtures/Providers/AdminPanelProvider.php +++ b/tests/Fixtures/Providers/AdminPanelProvider.php @@ -18,6 +18,7 @@ use Illuminate\View\Middleware\ShareErrorsFromSession; use Relaticle\CustomFields\CustomFieldsPlugin; use Relaticle\CustomFields\Tests\Fixtures\Pages\Settings; +use Relaticle\CustomFields\Tests\Fixtures\Resources\Comments\CommentResource; use Relaticle\CustomFields\Tests\Fixtures\Resources\Posts\PostResource; class AdminPanelProvider extends PanelProvider @@ -32,6 +33,7 @@ public function panel(Panel $panel): Panel ->passwordReset() ->emailVerification() ->resources([ + CommentResource::class, PostResource::class, ]) ->pages([ diff --git a/tests/Fixtures/Resources/Comments/CommentResource.php b/tests/Fixtures/Resources/Comments/CommentResource.php new file mode 100644 index 00000000..b804f984 --- /dev/null +++ b/tests/Fixtures/Resources/Comments/CommentResource.php @@ -0,0 +1,62 @@ +components([ + TextEntry::make('body'), + + CustomFields::infolist()->build(), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + TextColumn::make('body'), + ]) + ->recordActions([ + ViewAction::make(), + ]); + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListComments::route('/'), + 'view' => Pages\ViewComment::route('/{record}'), + ]; + } +} diff --git a/tests/Fixtures/Resources/Comments/Pages/ListComments.php b/tests/Fixtures/Resources/Comments/Pages/ListComments.php new file mode 100644 index 00000000..1cc97122 --- /dev/null +++ b/tests/Fixtures/Resources/Comments/Pages/ListComments.php @@ -0,0 +1,13 @@ +modifyQueryUsing(fn (Builder $query): Builder => $query->with('post.customFieldValues.customField')) + ->columns([ + TextColumn::make('body'), + + ...CustomFields::table()->forModel(Post::class)->through('post')->columns(), + ]) + ->filters([ + ...CustomFields::table()->forModel(Post::class)->through('post')->filters(), + ]); + } +} diff --git a/tests/Helpers.php b/tests/Helpers.php index 5b878444..c38ad477 100644 --- a/tests/Helpers.php +++ b/tests/Helpers.php @@ -2,15 +2,42 @@ declare(strict_types=1); +use Filament\Tables\Table; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Carbon; -use Relaticle\CustomFields\Contracts\EntityManagerInterface; +use Illuminate\Support\Facades\Schema; +use Livewire\Features\SupportTesting\Testable; +use Relaticle\CustomFields\Data\CustomFieldSettingsData; +use Relaticle\CustomFields\Data\VisibilityData; use Relaticle\CustomFields\EntitySystem\EntityConfigurator; use Relaticle\CustomFields\EntitySystem\EntityManager; use Relaticle\CustomFields\EntitySystem\EntityModel; +use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\Enums\EntityFeature; +use Relaticle\CustomFields\Enums\UiFlavor; +use Relaticle\CustomFields\Enums\UiSurface; +use Relaticle\CustomFields\Facades\CustomFields; +use Relaticle\CustomFields\FeatureSystem\FeatureConfigurator; +use Relaticle\CustomFields\FeatureSystem\FeatureManager; use Relaticle\CustomFields\Filament\Integration\Components\Forms\RecordSelectInput\RecordSelectInputComponent; +use Relaticle\CustomFields\Models\CustomField; +use Relaticle\CustomFields\Models\CustomFieldSection; +use Relaticle\CustomFields\Services\TenantContextService; +use Relaticle\CustomFields\Support\ViewFlavor; +use Relaticle\CustomFields\Tests\Fixtures\Livewire\ThroughTable; use Relaticle\CustomFields\Tests\Fixtures\Models\Post; +/** + * Whether a forked surface draws its polished view in this run. CI runs the whole suite once + * per flavor, so a test reading markup only one presentation has asks instead of assuming. + */ +function rendersPolished(UiSurface $surface): bool +{ + return ViewFlavor::flavor($surface) === UiFlavor::Polished; +} + /** * Replace the registered entities with a single lookup source and rebuild the registry. * @@ -33,7 +60,6 @@ function registerLookupEntity(string $modelClass, string $primaryAttribute, arra ); app()->forgetInstance(EntityManager::class); - app()->forgetInstance(EntityManagerInterface::class); } /** @@ -75,3 +101,127 @@ function recordSelectSearch(string $term, string $modelClass = Post::class): arr { return recordSelectFor($modelClass)->getSearchResultsForJs($term); } + +/** + * The features block exactly as the package ships it, bypassing the test environment's own. + */ +function shippedFeatureConfigurator(): FeatureConfigurator +{ + /** @var array{features: FeatureConfigurator} $config */ + $config = require dirname(__DIR__).'/config/custom-fields.php'; + + return $config['features']; +} + +/** + * A section for the given entity type, so fields created under it survive the activable scope. + */ +function sectionForEntity(string $entityType): CustomFieldSection +{ + $attributes = ['entity_type' => $entityType]; + + if (FeatureManager::isEnabled(CustomFieldsFeature::SYSTEM_MULTI_TENANCY)) { + $attributes[config('custom-fields.database.column_names.tenant_foreign_key')] = TenantContextService::getCurrentTenantId(); + } + + return CustomFieldSection::factory()->create($attributes); +} + +/** + * Rebuild the schema the multi-tenancy feature flag would have migrated, then enter a tenant. + * MySQL commits DDL implicitly, which ends the test transaction, so callers skip it there. + */ +function useTenantSchema(int|string $tenantId): void +{ + $tenantKey = config('custom-fields.database.column_names.tenant_foreign_key'); + + $tables = [ + config('custom-fields.database.table_names.custom_field_sections'), + config('custom-fields.database.table_names.custom_fields'), + config('custom-fields.database.table_names.custom_field_options'), + config('custom-fields.database.table_names.custom_field_values'), + config('custom-fields.database.table_names.custom_field_relationships'), + config('custom-fields.database.table_names.custom_field_links'), + ]; + + foreach ($tables as $table) { + Schema::table($table, function (Blueprint $blueprint) use ($tenantKey): void { + $blueprint->unsignedBigInteger($tenantKey)->nullable(); + }); + } + + // The definitions migration keys code per tenant when the flag is on at migrate time, so + // the rebuilt schema has to say the same: without it every tenant would share one + // namespace of relationship codes. + Schema::table(config('custom-fields.database.table_names.custom_field_relationships'), function (Blueprint $blueprint) use ($tenantKey): void { + $blueprint->dropUnique(['code']); + $blueprint->unique(['code', $tenantKey]); + }); + + // Eloquent caches each model's column listing statically to decide what is guardable, and + // an earlier test in this process cached these tables without their tenant column. The + // global afterEach in Pest.php clears it again for whatever runs next. + Closure::bind(static function (): void { + Model::$guardableColumns = []; + }, null, Model::class)(); + + config('custom-fields.features')->enable(CustomFieldsFeature::SYSTEM_MULTI_TENANCY); + + TenantContextService::setTenantId($tenantId); +} + +/** + * A list-visible text field on the given entity, for through-relation table surfaces. + */ +function throughTextField(string $entityClass, string $code, string $name, bool $searchable = false, ?VisibilityData $visibility = null): CustomField +{ + return CustomField::factory()->create([ + 'custom_field_section_id' => sectionForEntity($entityClass)->getKey(), + 'name' => $name, + 'code' => $code, + 'type' => 'text', + 'entity_type' => $entityClass, + 'settings' => new CustomFieldSettingsData( + visible_in_list: true, + list_toggleable_hidden: false, + searchable: $searchable, + visibility: $visibility ?? new VisibilityData, + ), + ]); +} + +/** + * Mount a table over rows that carry no custom fields of their own, reading the fields of + * the model reached through the given relation. + * + * @param class-string|Closure(): Builder $rows + * @param (Closure(Table): Table)|null $extend + */ +function throughTable(string|Closure $rows, string $sourceModel, string $relation, ?Closure $extend = null): Testable +{ + ThroughTable::$configureUsing = function (Table $table) use ($rows, $sourceModel, $relation, $extend): Table { + $table = $table + ->query(fn (): Builder => $rows instanceof Closure ? $rows() : $rows::query()) + ->columns([...CustomFields::table()->forModel($sourceModel)->through($relation)->columns()]) + ->filters([...CustomFields::table()->forModel($sourceModel)->through($relation)->filters()]); + + return $extend instanceof Closure ? $extend($table) : $table; + }; + + return livewire(ThroughTable::class); +} + +/** + * The same table without a through path, so a surface can be asserted from both sides. + * + * @param class-string|Closure(): Builder $rows + */ +function ownTable(string|Closure $rows, string $sourceModel): Testable +{ + ThroughTable::$configureUsing = fn (Table $table): Table => $table + ->query(fn (): Builder => $rows instanceof Closure ? $rows() : $rows::query()) + ->columns([...CustomFields::table()->forModel($sourceModel)->columns()]) + ->filters([...CustomFields::table()->forModel($sourceModel)->filters()]); + + return livewire(ThroughTable::class); +} diff --git a/tests/Pest.php b/tests/Pest.php index bbdaa603..9f7d3655 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -12,6 +12,14 @@ // Apply base test configuration to all tests uses(TestCase::class, RefreshDatabase::class)->in(__DIR__); +// Eloquent memoises each model's column listing to decide what is guardable, and a test that +// migrates a column mid-run would poison every test that follows it. +uses()->afterEach(function (): void { + Closure::bind(static function (): void { + Model::$guardableColumns = []; + }, null, Model::class)(); +})->in(__DIR__); + /** * Livewire testing helper - replacement for pest-plugin-livewire. * diff --git a/tests/TestCase.php b/tests/TestCase.php index c454dee9..7e5459ce 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,13 +23,13 @@ use Override; use Postare\BladeMdi\BladeMdiServiceProvider; use Propaganistas\LaravelPhone\PhoneServiceProvider; -use Relaticle\CustomFields\Contracts\EntityManagerInterface; use Relaticle\CustomFields\CustomFieldsServiceProvider; use Relaticle\CustomFields\EntitySystem\EntityConfigurator; use Relaticle\CustomFields\EntitySystem\EntityManager; use Relaticle\CustomFields\EntitySystem\EntityModel; use Relaticle\CustomFields\Enums\CustomFieldsFeature; use Relaticle\CustomFields\Enums\EntityFeature; +use Relaticle\CustomFields\Enums\UiFlavor; use Relaticle\CustomFields\FeatureSystem\FeatureConfigurator; use Relaticle\CustomFields\Tests\Database\Factories\TagFactory; use Relaticle\CustomFields\Tests\database\factories\UserFactory; @@ -108,24 +108,23 @@ protected function defineEnvironment($app): void __DIR__.'/../resources/views', ]); - // Database configuration - config()->set('database.default', 'testing'); - config()->set('database.connections.testing', [ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => '', - ]); + $this->configureDatabaseConnection(); // Authentication configuration for testing config()->set('auth.providers.users.model', User::class); + // The flavor is a run dimension, not a per-test one: CI runs the whole suite once per + // flavor, so the forked surfaces are exercised in both presentations. + config()->set('custom-fields.ui.flavor', env('CUSTOM_FIELDS_UI_FLAVOR', UiFlavor::Polished->value)); + // Custom fields configuration config()->set('custom-fields.database.table_names.custom_field_sections', 'custom_field_sections'); config()->set('custom-fields.database.table_names.custom_fields', 'custom_fields'); config()->set('custom-fields.database.table_names.custom_field_values', 'custom_field_values'); config()->set('custom-fields.database.table_names.custom_field_options', 'custom_field_options'); - // Enable all necessary features for testing + // Every flag is pinned, enabled or disabled, so the suite never rides the package + // defaults an unlisted flag falls back to. config()->set('custom-fields.features', FeatureConfigurator::configure() ->enable( CustomFieldsFeature::FIELD_CONDITIONAL_VISIBILITY, @@ -135,6 +134,22 @@ protected function defineEnvironment($app): void CustomFieldsFeature::UI_TABLE_FILTERS, CustomFieldsFeature::SYSTEM_MANAGEMENT_INTERFACE, CustomFieldsFeature::SYSTEM_SECTIONS, + CustomFieldsFeature::SYSTEM_RELATIONSHIPS, + ) + ->disable( + CustomFieldsFeature::FIELD_ENCRYPTION, + CustomFieldsFeature::FIELD_OPTION_COLORS, + CustomFieldsFeature::FIELD_CODE_AUTO_GENERATE, + CustomFieldsFeature::FIELD_MULTI_VALUE, + CustomFieldsFeature::FIELD_UNIQUE_VALUE, + CustomFieldsFeature::FIELD_VALIDATION_RULES, + CustomFieldsFeature::FIELD_DESCRIPTION, + CustomFieldsFeature::FIELD_DESCRIPTION_POSITION, + CustomFieldsFeature::SECTION_CONDITIONAL_VISIBILITY, + CustomFieldsFeature::UI_TOGGLEABLE_COLUMNS_HIDDEN_DEFAULT, + CustomFieldsFeature::UI_FIELD_WIDTH_CONTROL, + CustomFieldsFeature::UI_SECTION_WIDTH_CONTROL, + CustomFieldsFeature::SYSTEM_MULTI_TENANCY, ) ); @@ -150,6 +165,48 @@ protected function defineEnvironment($app): void config()->set('data.validation_strategy', 'only_requests'); } + /** + * Configure the `testing` connection from DB_CONNECTION (default sqlite in-memory). + * pgsql/mysql read DB_HOST/DB_PORT/DB_DATABASE/DB_USERNAME/DB_PASSWORD, set via real + * env vars or a phpunit.xml block. + */ + private function configureDatabaseConnection(): void + { + $driver = env('DB_CONNECTION', 'sqlite'); + + config()->set('database.default', 'testing'); + + config()->set('database.connections.testing', match ($driver) { + 'pgsql' => [ + 'driver' => 'pgsql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'custom_fields_test'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'search_path' => 'public', + ], + 'mysql' => [ + 'driver' => 'mysql', + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'custom_fields_test'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + ], + default => [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ], + }); + } + protected function defineDatabaseMigrations(): void { // Load package migrations @@ -221,6 +278,5 @@ protected function setEntityConditionRelations(array $conditionRelations): void protected function refreshEntityManager(): void { $this->app->forgetInstance(EntityManager::class); - $this->app->forgetInstance(EntityManagerInterface::class); } } diff --git a/tests/database/factories/TeamFactory.php b/tests/database/factories/TeamFactory.php deleted file mode 100644 index efbd68fb..00000000 --- a/tests/database/factories/TeamFactory.php +++ /dev/null @@ -1,19 +0,0 @@ - $this->faker->company(), - 'description' => $this->faker->sentence(), - ]; - } -} diff --git a/tests/database/migrations/create_posts_table.php b/tests/database/migrations/0001_create_posts_table.php similarity index 91% rename from tests/database/migrations/create_posts_table.php rename to tests/database/migrations/0001_create_posts_table.php index 8fea8905..ddefe36e 100644 --- a/tests/database/migrations/create_posts_table.php +++ b/tests/database/migrations/0001_create_posts_table.php @@ -1,5 +1,7 @@ softDeletes(); }); } - - public function down(): void - { - Schema::dropIfExists('posts'); - } }; diff --git a/tests/database/migrations/create_tags_table.php b/tests/database/migrations/0002_create_tags_table.php similarity index 93% rename from tests/database/migrations/create_tags_table.php rename to tests/database/migrations/0002_create_tags_table.php index d23f944a..ee98a2cd 100644 --- a/tests/database/migrations/create_tags_table.php +++ b/tests/database/migrations/0002_create_tags_table.php @@ -1,5 +1,7 @@ foreignId('post_id'); $table->foreignId('author_id'); $table->text('body'); + $table->nullableMorphs('commentable'); $table->timestamps(); }); } diff --git a/tests/database/migrations/create_post_tag_table.php b/tests/database/migrations/0004_create_post_tag_table.php similarity index 94% rename from tests/database/migrations/create_post_tag_table.php rename to tests/database/migrations/0004_create_post_tag_table.php index 29d04ddf..1dc25841 100644 --- a/tests/database/migrations/create_post_tag_table.php +++ b/tests/database/migrations/0004_create_post_tag_table.php @@ -1,5 +1,7 @@ id(); - $table->foreignId('team_id')->constrained(); - $table->foreignId('user_id')->constrained(); - $table->string('role')->nullable(); - $table->timestamps(); - }); - } - - public function down(): void - { - Schema::dropIfExists('team_user'); - } -}; diff --git a/tests/database/migrations/zz_restore_custom_fields_lookup_type.php b/tests/database/migrations/zz_restore_custom_fields_lookup_type.php new file mode 100644 index 00000000..739ff0b4 --- /dev/null +++ b/tests/database/migrations/zz_restore_custom_fields_lookup_type.php @@ -0,0 +1,32 @@ +string('lookup_type')->nullable(); + }); + } +};