diff --git a/packages/forms/resources/views/components/builder.blade.php b/packages/forms/resources/views/components/builder.blade.php index 94d42f4f03..7f65d952c8 100644 --- a/packages/forms/resources/views/components/builder.blade.php +++ b/packages/forms/resources/views/components/builder.blade.php @@ -90,21 +90,21 @@ $hasBlockNumbers = $hasBlockNumbers(); @endphp - @foreach ($items as $uuid => $item) + @foreach ($items as $itemKey => $item) @php $visibleExtraItemActions = array_filter( $extraItemActions, - fn (Action $action): bool => $action(['item' => $uuid])->isVisible(), + fn (Action $action): bool => $action(['item' => $itemKey])->isVisible(), ); - $cloneAction = $cloneAction(['item' => $uuid]); + $cloneAction = $cloneAction(['item' => $itemKey]); $cloneActionIsVisible = $isCloneable && $cloneAction->isVisible(); - $deleteAction = $deleteAction(['item' => $uuid]); + $deleteAction = $deleteAction(['item' => $itemKey]); $deleteActionIsVisible = $isDeletable && $deleteAction->isVisible(); - $editAction = $editAction(['item' => $uuid]); + $editAction = $editAction(['item' => $itemKey]); $editActionIsVisible = $hasBlockPreviews && $editAction->isVisible(); - $moveDownAction = $moveDownAction(['item' => $uuid])->disabled($loop->last); + $moveDownAction = $moveDownAction(['item' => $itemKey])->disabled($loop->last); $moveDownActionIsVisible = $isReorderableWithButtons && $moveDownAction->isVisible(); - $moveUpAction = $moveUpAction(['item' => $uuid])->disabled($loop->first); + $moveUpAction = $moveUpAction(['item' => $itemKey])->disabled($loop->first); $moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible(); $reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible(); @endphp @@ -117,7 +117,7 @@ x-on:builder-expand.window="$event.detail === '{{ $statePath }}' && (isCollapsed = false)" x-on:builder-collapse.window="$event.detail === '{{ $statePath }}' && (isCollapsed = true)" x-on:expand="isCollapsed = false" - x-sortable-item="{{ $uuid }}" + x-sortable-item="{{ $itemKey }}" class="fi-fo-builder-item" x-bind:class="{ 'fi-collapsed': isCollapsed }" > @@ -154,7 +154,7 @@ @endif @php - $blockIcon = $item->getParentComponent()->getIcon($item->getRawState(), $uuid); + $blockIcon = $item->getParentComponent()->getIcon($item->getRawState(), $itemKey); @endphp @if ($hasBlockIcons && filled($blockIcon)) @@ -168,7 +168,7 @@ 'fi-truncated' => $isBlockLabelTruncated, ]) > - {{ $item->getParentComponent()->getLabel($item->getRawState(), $uuid) }} + {{ $item->getParentComponent()->getLabel($item->getRawState(), $itemKey) }} @if ($hasBlockNumbers) {{ $loop->iteration }} @@ -182,7 +182,7 @@ > @foreach ($visibleExtraItemActions as $extraItemAction)
  • - {{ $extraItemAction(['item' => $uuid]) }} + {{ $extraItemAction(['item' => $itemKey]) }}
  • @endforeach @@ -248,7 +248,7 @@
    @endif @else @@ -258,20 +258,20 @@ @if (! $loop->last) - @if ($isAddable && $addBetweenAction(['afterItem' => $uuid])->isVisible()) + @if ($isAddable && $addBetweenAction(['afterItem' => $itemKey])->isVisible())
  • - {{ $addBetweenAction(['afterItem' => $uuid]) }} + {{ $addBetweenAction(['afterItem' => $itemKey]) }}
    diff --git a/packages/forms/resources/views/components/repeater/index.blade.php b/packages/forms/resources/views/components/repeater/index.blade.php index 4f33de7528..a4605ed659 100644 --- a/packages/forms/resources/views/components/repeater/index.blade.php +++ b/packages/forms/resources/views/components/repeater/index.blade.php @@ -86,20 +86,20 @@ ->class(['fi-fo-repeater-items']) }} > - @foreach ($items as $uuid => $item) + @foreach ($items as $itemKey => $item) @php - $itemLabel = $getItemLabel($uuid); + $itemLabel = $getItemLabel($itemKey); $visibleExtraItemActions = array_filter( $extraItemActions, - fn (Action $action): bool => $action(['item' => $uuid])->isVisible(), + fn (Action $action): bool => $action(['item' => $itemKey])->isVisible(), ); - $cloneAction = $cloneAction(['item' => $uuid]); + $cloneAction = $cloneAction(['item' => $itemKey]); $cloneActionIsVisible = $isCloneable && $cloneAction->isVisible(); - $deleteAction = $deleteAction(['item' => $uuid]); + $deleteAction = $deleteAction(['item' => $itemKey]); $deleteActionIsVisible = $isDeletable && $deleteAction->isVisible(); - $moveDownAction = $moveDownAction(['item' => $uuid])->disabled($loop->last); + $moveDownAction = $moveDownAction(['item' => $itemKey])->disabled($loop->last); $moveDownActionIsVisible = $isReorderableWithButtons && $moveDownAction->isVisible(); - $moveUpAction = $moveUpAction(['item' => $uuid])->disabled($loop->first); + $moveUpAction = $moveUpAction(['item' => $itemKey])->disabled($loop->first); $moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible(); $reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible(); @endphp @@ -112,7 +112,7 @@ x-on:repeater-expand.window="$event.detail === '{{ $statePath }}' && (isCollapsed = false)" x-on:repeater-collapse.window="$event.detail === '{{ $statePath }}' && (isCollapsed = true)" x-on:expand="isCollapsed = false" - x-sortable-item="{{ $uuid }}" + x-sortable-item="{{ $itemKey }}" class="fi-fo-repeater-item" x-bind:class="{ 'fi-collapsed': isCollapsed }" > @@ -165,7 +165,7 @@ > @foreach ($visibleExtraItemActions as $extraItemAction)
  • - {{ $extraItemAction(['item' => $uuid]) }} + {{ $extraItemAction(['item' => $itemKey]) }}
  • @endforeach @@ -213,10 +213,10 @@ @if (! $loop->last) - @if ($isAddable && $addBetweenAction(['afterItem' => $uuid])->isVisible()) + @if ($isAddable && $addBetweenAction(['afterItem' => $itemKey])->isVisible())
  • - {{ $addBetweenAction(['afterItem' => $uuid]) }} + {{ $addBetweenAction(['afterItem' => $itemKey]) }}
  • @elseif (filled($labelBetweenItems)) diff --git a/packages/forms/resources/views/components/repeater/simple.blade.php b/packages/forms/resources/views/components/repeater/simple.blade.php index 1e130b1946..0cad42e470 100644 --- a/packages/forms/resources/views/components/repeater/simple.blade.php +++ b/packages/forms/resources/views/components/repeater/simple.blade.php @@ -47,26 +47,26 @@ ->class(['fi-fo-repeater-items']) }} > - @foreach ($items as $uuid => $item) + @foreach ($items as $itemKey => $item) @php $visibleExtraItemActions = array_filter( $extraItemActions, - fn (Action $action): bool => $action(['item' => $uuid])->isVisible(), + fn (Action $action): bool => $action(['item' => $itemKey])->isVisible(), ); - $cloneAction = $cloneAction(['item' => $uuid]); + $cloneAction = $cloneAction(['item' => $itemKey]); $cloneActionIsVisible = $isCloneable && $cloneAction->isVisible(); - $deleteAction = $deleteAction(['item' => $uuid]); + $deleteAction = $deleteAction(['item' => $itemKey]); $deleteActionIsVisible = $isDeletable && $deleteAction->isVisible(); - $moveDownAction = $moveDownAction(['item' => $uuid])->disabled($loop->last); + $moveDownAction = $moveDownAction(['item' => $itemKey])->disabled($loop->last); $moveDownActionIsVisible = $isReorderableWithButtons && $moveDownAction->isVisible(); - $moveUpAction = $moveUpAction(['item' => $uuid])->disabled($loop->first); + $moveUpAction = $moveUpAction(['item' => $itemKey])->disabled($loop->first); $moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible(); $reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible(); @endphp
  • @@ -93,7 +93,7 @@ @foreach ($visibleExtraItemActions as $extraItemAction)
  • - {{ $extraItemAction(['item' => $uuid]) }} + {{ $extraItemAction(['item' => $itemKey]) }}
  • @endforeach diff --git a/packages/forms/src/Components/Builder.php b/packages/forms/src/Components/Builder.php index 2eec0c0958..d3778b8569 100644 --- a/packages/forms/src/Components/Builder.php +++ b/packages/forms/src/Components/Builder.php @@ -106,8 +106,8 @@ class Builder extends Field implements CanConcealComponents, HasExtraItemActions $items = []; foreach ($state ?? [] as $itemData) { - if ($uuid = $component->generateUuid()) { - $items[$uuid] = $itemData; + if ($key = $component->generateUuid()) { + $items[$key] = $itemData; } else { $items[] = $itemData; } @@ -1123,17 +1123,17 @@ class Builder extends Field implements CanConcealComponents, HasExtraItemActions /** * @return array */ - public function getItemState(string $uuid): array + public function getItemState(string $key): array { - return $this->getChildSchema($uuid)->getState(shouldCallHooksBefore: false); + return $this->getChildSchema($key)->getState(shouldCallHooksBefore: false); } /** * @return array */ - public function getRawItemState(string $uuid): array + public function getRawItemState(string $key): array { - return $this->getChildSchema($uuid)->getRawState(); + return $this->getChildSchema($key)->getRawState(); } public function getHeadingsCount(): int diff --git a/packages/forms/src/Components/Builder/Block.php b/packages/forms/src/Components/Builder/Block.php index 7081c199e1..1c529d7216 100644 --- a/packages/forms/src/Components/Builder/Block.php +++ b/packages/forms/src/Components/Builder/Block.php @@ -73,11 +73,11 @@ class Block extends Component /** * @param array | null $state */ - public function getLabel(?array $state = null, ?string $uuid = null): string | Htmlable + public function getLabel(?array $state = null, ?string $key = null): string | Htmlable { return $this->evaluate( $this->label, - ['state' => $state, 'uuid' => $uuid], + ['key' => $key, 'state' => $state, 'uuid' => $key], ) ?? $this->getDefaultLabel(); } } diff --git a/packages/forms/src/Components/Repeater.php b/packages/forms/src/Components/Repeater.php index e078dc93c0..022347378b 100644 --- a/packages/forms/src/Components/Repeater.php +++ b/packages/forms/src/Components/Repeater.php @@ -135,8 +135,8 @@ class Repeater extends Field implements CanConcealComponents, HasExtraItemAction $itemData = [$simpleField->getName() => $itemData]; } - if ($uuid = $component->generateUuid()) { - $items[$uuid] = $itemData; + if ($key = $component->generateUuid()) { + $items[$key] = $itemData; } else { $items[] = $itemData; } @@ -710,8 +710,8 @@ class Repeater extends Field implements CanConcealComponents, HasExtraItemAction $itemData = [$simpleField->getName() => $itemData]; } - if ($uuid = $component->generateUuid()) { - $items[$uuid] = $itemData; + if ($key = $component->generateUuid()) { + $items[$key] = $itemData; } else { $items[] = $itemData; } @@ -1146,15 +1146,17 @@ class Repeater extends Field implements CanConcealComponents, HasExtraItemAction ); } - public function getItemLabel(string $uuid): string | Htmlable | null + public function getItemLabel(string $key): string | Htmlable | null { - $container = $this->getChildSchema($uuid); + $container = $this->getChildSchema($key); return $this->evaluate($this->itemLabel, [ 'container' => $container, 'item' => $container, + 'key' => $key, + 'schema' => $container, 'state' => $container->getRawState(), - 'uuid' => $uuid, + 'uuid' => $key, ]); } @@ -1303,17 +1305,17 @@ class Repeater extends Field implements CanConcealComponents, HasExtraItemAction /** * @return array */ - public function getItemState(string $uuid): array + public function getItemState(string $key): array { - return $this->getChildSchema($uuid)->getState(shouldCallHooksBefore: false); + return $this->getChildSchema($key)->getState(shouldCallHooksBefore: false); } /** * @return array */ - public function getRawItemState(string $uuid): array + public function getRawItemState(string $key): array { - return $this->getChildSchema($uuid)->getRawState(); + return $this->getChildSchema($key)->getRawState(); } public function getHeadingsCount(): int diff --git a/packages/schemas/docs/02-forms/12-repeater.md b/packages/schemas/docs/02-forms/12-repeater.md index 2108eb3b2c..11c55a43b6 100644 --- a/packages/schemas/docs/02-forms/12-repeater.md +++ b/packages/schemas/docs/02-forms/12-repeater.md @@ -332,6 +332,8 @@ Repeater::make('qualifications') ->orderColumn('order_column') ``` +As well as allowing a static value, the `orderColumn()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ### Integrating with a `BelongsToMany` Eloquent relationship There is a common misconception that using a `BelongsToMany` relationship with a repeater is as simple as using a `HasMany` relationship. This is not the case, as a `BelongsToMany` relationship requires a pivot table to store the relationship data. The repeater saves its data to the related model, not the pivot table. Therefore, if you want to map each repeater item to a row in the pivot table, you must use a `HasMany` relationship with a pivot model to use a repeater with a `BelongsToMany` relationship. @@ -369,7 +371,9 @@ class OrderProduct extends Pivot } ``` -> Please ensure that your pivot model has a primary key column, like `id`, to allow Filament to keep track of which repeater items have been created, updated and deleted. To make sure that Filament keeps track of the primary key, the pivot model needs to have the `$incrementing` property set to `true`. + Now you can use the `orderProducts` relationship with the repeater, and it will save the data to the `order_product` pivot table: @@ -406,6 +410,8 @@ Repeater::make('qualifications') }) ``` +You can inject various utilities into the function passed to `mutateRelationshipDataBeforeFillUsing()` as parameters. + ### Mutating related item data before creating You may mutate the data for a new related item before it is created in the database using the `mutateRelationshipDataBeforeCreateUsing()` method. This method accepts a closure that receives the current item's data in a `$data` variable. You can choose to return either the modified array of data, or `null` to prevent the item from being created: @@ -425,6 +431,8 @@ Repeater::make('qualifications') }) ``` +You can inject various utilities into the function passed to `mutateRelationshipDataBeforeCreateUsing()` as parameters. + ### Mutating related item data before saving You may mutate the data for an existing related item before it is saved in the database using the `mutateRelationshipDataBeforeSaveUsing()` method. This method accepts a closure that receives the current item's data in a `$data` variable. You can choose to return either the modified array of data, or `null` to prevent the item from being saved: @@ -444,6 +452,8 @@ Repeater::make('qualifications') }) ``` +You can inject various utilities into the function passed to `mutateRelationshipDataBeforeSaveUsing()` as parameters. + ## Grid layout You may organize repeater items into columns by using the `grid()` method: @@ -460,7 +470,9 @@ Repeater::make('qualifications') -This method accepts the same options as the `columns()` method of the [grid](../../schemas/layouts/grid). This allows you to responsively customize the number of grid columns at various breakpoints. +This method accepts the same options as the `columns()` method of the [grid](../layouts/grid). This allows you to responsively customize the number of grid columns at various breakpoints. + +As well as allowing a static value, the `grid()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. ## Adding a label to repeater items based on their content @@ -488,7 +500,11 @@ Repeater::make('members') ->itemLabel(fn (array $state): ?string => $state['name'] ?? null), ``` -Any fields that you use from `$state` should be `live()` if you wish to see the item label update live as you use the form. + + +You can inject various utilities into the function passed to `itemLabel()` as parameters. @@ -508,6 +524,8 @@ Repeater::make('invitations') ) ``` +As well as allowing a static value, the `simple()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + Instead of using a nested array to store data, simple repeaters use a flat array of values. This means that the data structure for the above example could look like this: @@ -523,7 +541,7 @@ Instead of using a nested array to store data, simple repeaters use a flat array ## Using `$get()` to access parent field values -All form components are able to [use `$get()` and `$set()`](../advanced) to access another field's value. However, you might experience unexpected behavior when using this inside the repeater's schema. +All form components are able to [use `$get()` and `$set()`](overview#injecting-the-state-of-another-field) to access another field's value. However, you might experience unexpected behavior when using this inside the repeater's schema. This is because `$get()` and `$set()`, by default, are scoped to the current repeater item. This means that you are able to interact with another field inside that repeater item easily without knowing which repeater item the current form component belongs to. @@ -570,6 +588,8 @@ Repeater::make('members') ->maxItems(5) ``` +As well as allowing static values, the `minItems()` and `maxItems()` methods also accept a function to dynamically calculate them. You can inject various utilities into the function as parameters. + ### Distinct state validation In many cases, you will want to ensure some sort of uniqueness between repeater items. A couple of common examples could be: @@ -596,6 +616,17 @@ The behavior of the `distinct()` validation depends on the data type that the fi - If the field returns a boolean, like a [checkbox](checkbox) or [toggle](toggle), the validation will ensure that only one item has a value of `true`. There may be many fields in the repeater that have a value of `false`. - Otherwise, for fields like a [select](select), [radio](radio), [checkbox list](checkbox-list), or [toggle buttons](toggle-buttons), the validation will ensure that each option may only be selected once across all items in the repeater. +Optionally, you may pass a boolean value to the `distinct()` method to control if the field should be distinct or not: + +```php +use Filament\Forms\Components\Checkbox; + +Checkbox::make('is_correct') + ->distinct(FeatureFlag::active()) +``` + +As well as allowing a static value, the `distinct()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + #### Automatically fixing indistinct state If you'd like to automatically fix indistinct state, you can use the `fixIndistinctState()` method: @@ -619,6 +650,17 @@ Depending on the data type that the field handles, the behavior of the `fixIndis - If the field returns a boolean, like a [checkbox](checkbox) or [toggle](toggle), and one of the fields is enabled, Filament will automatically disable all other enabled fields on behalf of the user. - Otherwise, for fields like a [select](select), [radio](radio), [checkbox list](checkbox-list), or [toggle buttons](toggle-buttons), when a user selects an option, Filament will automatically deselect all other usages of that option on behalf of the user. +Optionally, you may pass a boolean value to the `fixIndistinctState()` method to control if the field should fix indistinct state or not: + +```php +use Filament\Forms\Components\Checkbox; + +Checkbox::make('is_correct') + ->fixIndistinctState(FeatureFlag::active()) +``` + +As well as allowing a static value, the `fixIndistinctState()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + #### Disabling options when they are already selected in another item If you'd like to disable options in a [select](select), [radio](radio), [checkbox list](checkbox-list), or [toggle buttons](toggle-buttons) when they are already selected in another item, you can use the `disableOptionsWhenSelectedInSiblingRepeaterItems()` method: @@ -639,22 +681,24 @@ Repeater::make('members') This method will automatically enable the `distinct()` and `live()` methods on the field. -In case you want to add another condition to [disable options](../select#disabling-specific-options) with, you can chain `disableOptionWhen()` with the `merge: true` argument: - -```php -use Filament\Forms\Components\Repeater; -use Filament\Forms\Components\Select; - -Repeater::make('members') - ->schema([ - Select::make('role') - ->options([ - // ... - ]) - ->disableOptionsWhenSelectedInSiblingRepeaterItems() - ->disableOptionWhen(fn (string $value): bool => $value === 'super_admin', merge: true), - ]) -``` + ## Customizing the repeater item actions @@ -686,6 +730,8 @@ Repeater::make('members') ) ``` +The action registration methods can inject various utilities into the function as parameters. + ### Confirming repeater actions with a modal You can confirm actions with a modal by using the `requiresConfirmation()` method on the action object. You may use any [modal customization method](../../actions/modals) to change its content and behavior: @@ -703,11 +749,13 @@ Repeater::make('members') ) ``` -> The `collapseAction()`, `collapseAllAction()`, `expandAction()`, `expandAllAction()` and `reorderAction()` methods do not support confirmation modals, as clicking their buttons does not make the network request that is required to show the modal. + ### Adding extra item actions to a repeater -You may add new [action buttons](../../schemas/actions) to the header of each repeater item by passing `Action` objects into `extraItemActions()`: +You may add new [action buttons](../primes/actions-in-schemas) to the header of each repeater item by passing `Action` objects into `extraItemActions()`: ```php use Filament\Actions\Action; diff --git a/packages/schemas/docs/02-forms/13-builder.md b/packages/schemas/docs/02-forms/13-builder.md index 215f355663..cbb075a771 100644 --- a/packages/schemas/docs/02-forms/13-builder.md +++ b/packages/schemas/docs/02-forms/13-builder.md @@ -1,6 +1,7 @@ --- title: Builder --- +import Aside from "@components/Aside.astro" import AutoScreenshot from "@components/AutoScreenshot.astro" import UtilityInjection from "@components/UtilityInjection.astro" @@ -12,6 +13,7 @@ The primary use of the builder component is to build web page content using pred ```php use Filament\Forms\Components\Builder; +use Filament\Forms\Components\Builder\Block; use Filament\Forms\Components\FileUpload; use Filament\Forms\Components\Select; use Filament\Forms\Components\Textarea; @@ -19,7 +21,7 @@ use Filament\Forms\Components\TextInput; Builder::make('content') ->blocks([ - Builder\Block::make('heading') + Block::make('heading') ->schema([ TextInput::make('content') ->label('Heading') @@ -36,13 +38,13 @@ Builder::make('content') ->required(), ]) ->columns(2), - Builder\Block::make('paragraph') + Block::make('paragraph') ->schema([ Textarea::make('content') ->label('Paragraph') ->required(), ]), - Builder\Block::make('image') + Block::make('image') ->schema([ FileUpload::make('url') ->label('Image') @@ -63,11 +65,12 @@ As evident in the above example, blocks can be defined within the `blocks()` met ```php use Filament\Forms\Components\Builder; +use Filament\Forms\Components\Builder\Block; use Filament\Forms\Components\TextInput; Builder::make('content') ->blocks([ - Builder\Block::make('heading') + Block::make('heading') ->schema([ TextInput::make('content')->required(), // ... @@ -81,9 +84,9 @@ Builder::make('content') By default, the label of the block will be automatically determined based on its name. To override the block's label, you may use the `label()` method. Customizing the label in this way is useful if you wish to use a [translation string for localization](https://laravel.com/docs/localization#retrieving-translation-strings): ```php -use Filament\Forms\Components\Builder; +use Filament\Forms\Components\Builder\Block; -Builder\Block::make('heading') +Block::make('heading') ->label(__('blocks.heading')) ``` @@ -92,10 +95,10 @@ Builder\Block::make('heading') You may add a label for a builder item using the same `label()` method. This method accepts a closure that receives the item's data in a `$state` variable. If `$state` is null, you should return the block label that should be displayed in the block picker. Otherwise, you should return a string to be used as the item label: ```php -use Filament\Forms\Components\Builder; +use Filament\Forms\Components\Builder\Block; use Filament\Forms\Components\TextInput; -Builder\Block::make('heading') +Block::make('heading') ->schema([ TextInput::make('content') ->live(onBlur: true) @@ -113,6 +116,8 @@ Builder\Block::make('heading') Any fields that you use from `$state` should be `live()` if you wish to see the item label update live as you use the form. +You can inject various utilities into the function passed to `label()` as parameters. + ### Numbering builder items @@ -129,17 +134,21 @@ Builder::make('content') ->blockNumbers(false) ``` +As well as allowing a static value, the `blockNumbers()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ## Setting a block's icon Blocks may also have an [icon](../../styling/icons), which is displayed next to the label. You can add an icon by passing its name to the `icon()` method: ```php -use Filament\Forms\Components\Builder; +use Filament\Forms\Components\Builder\Block; -Builder\Block::make('paragraph') +Block::make('paragraph') ->icon('heroicon-m-bars-3-bottom-left') ``` +As well as allowing a static value, the `icon()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ### Adding icons to the header of blocks @@ -156,6 +165,79 @@ Builder::make('content') ->blockIcons() ``` +Optionally, you may pass a boolean value to the `blockIcons()` method to control if the icons are displayed in the block headers: + +```php +use Filament\Forms\Components\Builder; + +Builder::make('content') + ->blocks([ + // ... + ]) + ->blockIcons(FeatureFlag::active()) +``` + +As well as allowing a static value, the `blockIcons()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + +## Previewing blocks + +If you prefer to render read-only previews in the builder instead of the blocks' forms, you can use the `blockPreviews()` method. This will render each block's `preview()` instead of the form. Block data will be passed to the preview Blade view in a variable with the same name: + +```php +use Filament\Forms\Components\Builder; +use Filament\Forms\Components\Builder\Block; +use Filament\Forms\Components\TextInput; + +Builder::make('content') + ->blockPreviews() + ->blocks([ + Block::make('heading') + ->schema([ + TextInput::make('text') + ->placeholder('Default heading'), + ]) + ->preview('filament.content.block-previews.heading'), + ]) +``` + +In `/resources/views/filament/content/block-previews/heading.blade.php`, you can access the block data like so: + +```blade +

    + {{ $text ?? 'Default heading' }} +

    +``` + +Optionally, the `blockPreviews()` method accepts a boolean value to control if the builder should render block previews or not: + +```php +use Filament\Forms\Components\Builder; + +Builder::make('content') + ->blocks([ + // ... + ]) + ->blockPreviews(FeatureFlag::active()) +``` + +As well as allowing static values, the `blockPreviews()` and `preview()` methods also accept functions to dynamically calculate them. You can inject various utilities into the function as parameters. + +### Interactive block previews + +By default, preview content is not interactive, and clicking it will open the Edit modal for that block to manage its settings. If you have links and buttons that you'd like to remain interactive in the block previews, you can use the `areInteractive: true` argument of the `blockPreviews()` method: + +```php +use Filament\Forms\Components\Builder; + +Builder::make('content') + ->blockPreviews(areInteractive: true) + ->blocks([ + // + ]) +``` + +As well as allowing a static value, the `areInteractive` argument also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ## Adding items An action button is displayed below the builder to allow the user to add a new item. @@ -174,6 +256,8 @@ Builder::make('content') ->addActionLabel('Add a new block') ``` +As well as allowing a static value, the `addActionLabel()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ### Aligning the add action button By default, the add action is aligned in the center. You may adjust this using the `addActionAlignment()` method, passing an `Alignment` option of `Alignment::Start` or `Alignment::End`: @@ -189,6 +273,8 @@ Builder::make('content') ->addActionAlignment(Alignment::Start) ``` +As well as allowing a static value, the `addActionAlignment()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ### Preventing the user from adding items You may prevent the user from adding items to the builder using the `addable(false)` method: @@ -203,6 +289,8 @@ Builder::make('content') ->addable(false) ``` +As well as allowing a static value, the `addable()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ## Deleting items An action button is displayed on each item to allow the user to delete it. @@ -221,6 +309,8 @@ Builder::make('content') ->deletable(false) ``` +As well as allowing a static value, the `deletable()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ## Reordering items A button is displayed on each item to allow the user to drag and drop to reorder it in the list. @@ -239,6 +329,8 @@ Builder::make('content') ->reorderable(false) ``` +As well as allowing a static value, the `reorderable()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ### Reordering items with buttons You may use the `reorderableWithButtons()` method to enable reordering items with buttons to move the item up and down: @@ -255,6 +347,20 @@ Builder::make('content') +Optionally, you may pass a boolean value to control if the builder should be ordered with buttons or not: + +```php +use Filament\Forms\Components\Builder; + +Builder::make('content') + ->blocks([ + // ... + ]) + ->reorderableWithButtons(FeatureFlag::active()) +``` + +As well as allowing a static value, the `reorderableWithButtons()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ### Preventing reordering with drag and drop You may use the `reorderableWithDragAndDrop(false)` method to prevent items from being ordered with drag and drop: @@ -269,6 +375,8 @@ Builder::make('content') ->reorderableWithDragAndDrop(false) ``` +As well as allowing a static value, the `reorderableWithDragAndDrop()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ## Collapsing items The builder may be `collapsible()` to optionally hide content in long forms: @@ -297,6 +405,21 @@ Builder::make('content') +Optionally, the `collaptible()` and `collapsed()` methods accept a boolean value to control if the builder should be collapsible and collapsed or not: + +```php +use Filament\Forms\Components\Builder; + +Builder::make('content') + ->blocks([ + // ... + ]) + ->collapsible(FeatureFlag::active()) + ->collapsed(FeatureFlag::active()) +``` + +As well as allowing static values, the `collapsible()` and `collapsed()` methods also accept functions to dynamically calculate it. You can inject various utilities into the function as parameters. + ## Cloning items You may allow builder items to be duplicated using the `cloneable()` method: @@ -311,7 +434,7 @@ Builder::make('content') ->cloneable() ``` - + ## Customizing the block picker @@ -336,6 +459,8 @@ This method can be used in a couple of different ways: Breakpoints (`sm`, `md`, `lg`, `xl`, `2xl`) are defined by Tailwind, and can be found in the [Tailwind documentation](https://tailwindcss.com/docs/responsive-design#overview). +As well as allowing a static value, the `blockPickerColumns()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ### Increasing the width of the block picker When you [increase the number of columns](#changing-the-number-of-columns-in-the-block-picker), the width of the dropdown should increase incrementally to handle the additional columns. If you'd like more control, you can manually set a maximum width for the dropdown using the `blockPickerWidth()` method. Options correspond to [Tailwind's max-width scale](https://tailwindcss.com/docs/max-width). The options are `xs`, `sm`, `md`, `lg`, `xl`, `2xl`, `3xl`, `4xl`, `5xl`, `6xl`, `7xl`: @@ -351,42 +476,27 @@ Builder::make() ]) ``` +As well as allowing a static value, the `blockPickerWidth()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. + ## Limiting the number of times a block can be used By default, each block can be used in the builder an unlimited number of times. You may limit this using the `maxItems()` method on a block: ```php -use Filament\Forms\Components\Builder; +use Filament\Forms\Components\Builder\Block; -Builder\Block::make('heading') +Block::make('heading') ->schema([ // ... ]) ->maxItems(1) ``` -## Builder validation - -As well as all rules listed on the [validation](../validation) page, there are additional rules that are specific to builders. - -### Number of items validation - -You can validate the minimum and maximum number of items that you can have in a builder by setting the `minItems()` and `maxItems()` methods: - -```php -use Filament\Forms\Components\Builder; - -Builder::make('content') - ->blocks([ - // ... - ]) - ->minItems(1) - ->maxItems(5) -``` +As well as allowing a static value, the `maxItems()` method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters. ## Using `$get()` to access parent field values -All form components are able to [use `$get()` and `$set()`](../advanced) to access another field's value. However, you might experience unexpected behavior when using this inside the builder's schema. +All form components are able to [use `$get()` and `$set()`](overview#injecting-the-state-of-another-field) to access another field's value. However, you might experience unexpected behavior when using this inside the builder's schema. This is because `$get()` and `$set()`, by default, are scoped to the current builder item. This means that you are able to interact with another field inside that builder item easily without knowing which builder item the current form component belongs to. @@ -414,6 +524,27 @@ You can use `../` to go up a level in the data structure, so `$get('../client_id The special case of `$get()` with no arguments, or `$get('')` or `$get('./')`, will always return the full data array for the current builder item. +## Builder validation + +As well as all rules listed on the [validation](../validation) page, there are additional rules that are specific to builders. + +### Number of items validation + +You can validate the minimum and maximum number of items that you can have in a builder by setting the `minItems()` and `maxItems()` methods: + +```php +use Filament\Forms\Components\Builder; + +Builder::make('content') + ->blocks([ + // ... + ]) + ->minItems(1) + ->maxItems(5) +``` + +As well as allowing static values, the `minItems()` and `maxItems()` methods also accept a function to dynamically calculate them. You can inject various utilities into the function as parameters. + ## Customizing the builder item actions This field uses action objects for easy customization of buttons within it. You can customize these buttons by passing a function to an action registration method. The function has access to the `$action` object, which you can use to [customize it](../../actions/trigger-button). The following methods are available to customize the actions: @@ -445,6 +576,8 @@ Builder::make('content') ) ``` +The action registration methods can inject various utilities into the function as parameters. + ### Confirming builder actions with a modal You can confirm actions with a modal by using the `requiresConfirmation()` method on the action object. You may use any [modal customization method](../../actions/modals) to change its content and behavior: @@ -462,21 +595,24 @@ Builder::make('content') ) ``` -> The `addAction()`, `addBetweenAction()`, `collapseAction()`, `collapseAllAction()`, `expandAction()`, `expandAllAction()` and `reorderAction()` methods do not support confirmation modals, as clicking their buttons does not make the network request that is required to show the modal. + ### Adding extra item actions to a builder -You may add new [action buttons](../../schemas/actions) to the header of each builder item by passing `Action` objects into `extraItemActions()`: +You may add new [action buttons](../primes/actions-in-schemas) to the header of each builder item by passing `Action` objects into `extraItemActions()`: ```php use Filament\Actions\Action; use Filament\Forms\Components\Builder; +use Filament\Forms\Components\Builder\Block; use Filament\Forms\Components\TextInput; use Illuminate\Support\Facades\Mail; Builder::make('content') ->blocks([ - Builder\Block::make('contactDetails') + Block::make('contactDetails') ->schema([ TextInput::make('email') ->label('Email address') @@ -524,49 +660,6 @@ $state[Str::uuid()] = [ $component->state($state); ``` -## Previewing blocks - -If you prefer to render read-only previews in the builder instead of the blocks' forms, you can use the `blockPreviews()` method. This will render each block's `preview()` instead of the form. Block data will be passed to the preview Blade view in a variable with the same name: - -```php -use Filament\Forms\Components\Builder; -use Filament\Forms\Components\Builder\Block; -use Filament\Forms\Components\TextInput; - -Builder::make('content') - ->blockPreviews() - ->blocks([ - Block::make('heading') - ->schema([ - TextInput::make('text') - ->placeholder('Default heading'), - ]) - ->preview('filament.content.block-previews.heading'), - ]) -``` - -In `/resources/views/filament/content/block-previews/heading.blade.php`, you can access the block data like so: - -```blade -

    - {{ $text ?? 'Default heading' }} -

    -``` - -### Interactive block previews - -By default, preview content is not interactive, and clicking it will open the Edit modal for that block to manage its settings. If you have links and buttons that you'd like to remain interactive in the block previews, you can use the `areInteractive: true` argument of the `blockPreviews()` method: - -```php -use Filament\Forms\Components\Builder; - -Builder::make('content') - ->blockPreviews(areInteractive: true) - ->blocks([ - // - ]) -``` - ## Testing builders Internally, builders generate UUIDs for items to keep track of them in the Livewire HTML easier. This means that when you are testing a form with a builder, you need to ensure that the UUIDs are consistent between the form and the test. This can be tricky, and if you don't do it correctly, your tests can fail as the tests are expecting a UUID, not a numeric key.