Repo rebuild

This commit is contained in:
Mark Gregory
2026-09-15 12:33:28 +01:00
commit be28c4feec
5629 changed files with 317074 additions and 0 deletions
@@ -0,0 +1,275 @@
@php
use Filament\Actions\Action;
use Filament\Support\Enums\Alignment;
$fieldWrapperView = $getFieldWrapperView();
$items = $getItems();
$blockPickerBlocks = $getBlockPickerBlocks();
$blockPickerColumns = $getBlockPickerColumns();
$blockPickerWidth = $getBlockPickerWidth();
$hasBlockPreviews = $hasBlockPreviews();
$hasInteractiveBlockPreviews = $hasInteractiveBlockPreviews();
$addAction = $getAction($getAddActionName());
$addActionAlignment = $getAddActionAlignment();
$addBetweenAction = $getAction($getAddBetweenActionName());
$cloneAction = $getAction($getCloneActionName());
$collapseAllAction = $getAction($getCollapseAllActionName());
$editAction = $getAction($getEditActionName());
$expandAllAction = $getAction($getExpandAllActionName());
$deleteAction = $getAction($getDeleteActionName());
$moveDownAction = $getAction($getMoveDownActionName());
$moveUpAction = $getAction($getMoveUpActionName());
$reorderAction = $getAction($getReorderActionName());
$extraItemActions = $getExtraItemActions();
$isAddable = $isAddable();
$isCloneable = $isCloneable();
$isCollapsible = $isCollapsible();
$isDeletable = $isDeletable();
$isReorderableWithButtons = $isReorderableWithButtons();
$isReorderableWithDragAndDrop = $isReorderableWithDragAndDrop();
$collapseAllActionIsVisible = $isCollapsible && $collapseAllAction->isVisible();
$expandAllActionIsVisible = $isCollapsible && $expandAllAction->isVisible();
$persistCollapsed = $shouldPersistCollapsed();
$key = $getKey();
$statePath = $getStatePath();
$blockLabelHeadingTag = $getHeadingTag();
$isBlockLabelTruncated = $isBlockLabelTruncated();
$labelBetweenItems = $getLabelBetweenItems();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<div
{{ $attributes->merge($getExtraAttributes(), escape: false)->class(['fi-fo-builder', 'fi-collapsible' => $isCollapsible]) }}>
@if ($collapseAllActionIsVisible || $expandAllActionIsVisible)
<div @class(['fi-fo-builder-actions', 'fi-hidden' => count($items) < 2])>
@if ($collapseAllActionIsVisible)
<span x-on:click="$dispatch('builder-collapse', '{{ $statePath }}')">
{{ $collapseAllAction }}
</span>
@endif
@if ($expandAllActionIsVisible)
<span x-on:click="$dispatch('builder-expand', '{{ $statePath }}')">
{{ $expandAllAction }}
</span>
@endif
</div>
@endif
@if (count($items))
<ul x-sortable data-sortable-animation-duration="{{ $getReorderAnimationDuration() }}"
x-on:end.stop="
$wire.mountAction(
'reorder',
{ items: $event.target.sortable.toArray() },
{ schemaComponent: '{{ $key }}' },
)
"
class="fi-fo-builder-items">
@php
$hasBlockLabels = $hasBlockLabels();
$hasBlockIcons = $hasBlockIcons();
$hasBlockNumbers = $hasBlockNumbers();
$hasBlockHeaders = $hasBlockHeaders();
@endphp
@foreach ($items as $itemKey => $item)
@php
$visibleExtraItemActions = array_filter(
$extraItemActions,
fn(Action $action): bool => $action(['item' => $itemKey])->isVisible(),
);
$cloneAction = $cloneAction(['item' => $itemKey]);
$cloneActionIsVisible = $isCloneable && $cloneAction->isVisible();
$deleteAction = $deleteAction(['item' => $itemKey]);
$deleteActionIsVisible = $isDeletable && $deleteAction->isVisible();
$editAction = $editAction(['item' => $itemKey]);
$editActionIsVisible = $hasBlockPreviews && $editAction->isVisible();
$moveDownAction = $moveDownAction(['item' => $itemKey])->disabled($loop->last);
$moveDownActionIsVisible = $isReorderableWithButtons && $moveDownAction->isVisible();
$moveUpAction = $moveUpAction(['item' => $itemKey])->disabled($loop->first);
$moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible();
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
$hasItemHeader =
$hasBlockHeaders &&
($reorderActionIsVisible ||
$moveUpActionIsVisible ||
$moveDownActionIsVisible ||
$hasBlockIcons ||
$hasBlockLabels ||
$editActionIsVisible ||
$cloneActionIsVisible ||
$deleteActionIsVisible ||
$isCollapsible ||
$visibleExtraItemActions);
@endphp
<li wire:ignore.self wire:key="{{ $item->getLivewireKey() }}.item" x-data="{
isCollapsed: @if ($persistCollapsed) $persist(@js($isCollapsed($item))).as(`builder-${@js($key)}-${@js($itemKey)}-isCollapsed`) @else @js($isCollapsed($item)) @endif,
}"
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="{{ $itemKey }}"
{{ $item->getParentComponent()->getExtraAttributeBag()->class(['fi-fo-builder-item', 'fi-fo-builder-item-has-header' => $hasItemHeader]) }}
x-bind:class="{ 'fi-collapsed': isCollapsed }">
@if ($hasItemHeader)
<div @if ($isCollapsible) x-on:click.stop="isCollapsed = !isCollapsed" @endif
class="fi-fo-builder-item-header">
@if ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible)
<ul class="fi-fo-builder-item-header-start-actions">
@if ($reorderActionIsVisible)
<li x-on:click.stop>
{{ $reorderAction->extraAttributes(['x-sortable-handle' => true], merge: true) }}
</li>
@endif
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
<li x-on:click.stop>
{{ $moveUpAction }}
</li>
<li x-on:click.stop>
{{ $moveDownAction }}
</li>
@endif
</ul>
@endif
@php
$blockIcon = $item->getParentComponent()->getIcon($item->getRawState(), $itemKey);
@endphp
@if ($hasBlockIcons && filled($blockIcon))
{{ \Filament\Support\generate_icon_html($blockIcon, attributes: new \Illuminate\View\ComponentAttributeBag()->class(['fi-fo-builder-item-header-icon'])) }}
@endif
@if ($hasBlockLabels)
<{{ $blockLabelHeadingTag }} @class([
'fi-fo-builder-item-header-label',
'fi-truncated' => $isBlockLabelTruncated,
])>
{{ $item->getParentComponent()->getLabel($item->getRawState(), $itemKey, $loop->index) }}
@if ($hasBlockNumbers)
{{ $loop->iteration }}
@endif
</{{ $blockLabelHeadingTag }}>
@endif
@if (
$editActionIsVisible ||
$cloneActionIsVisible ||
$deleteActionIsVisible ||
$isCollapsible ||
$visibleExtraItemActions)
<ul class="fi-fo-builder-item-header-end-actions">
@foreach ($visibleExtraItemActions as $extraItemAction)
<li x-on:click.stop>
{{ $extraItemAction(['item' => $itemKey]) }}
</li>
@endforeach
@if ($editActionIsVisible)
<li x-on:click.stop>
{{ $editAction }}
</li>
@endif
@if ($cloneActionIsVisible)
<li x-on:click.stop>
{{ $cloneAction }}
</li>
@endif
@if ($deleteActionIsVisible)
<li x-on:click.stop>
{{ $deleteAction }}
</li>
@endif
@if ($isCollapsible)
<li class="fi-fo-builder-item-header-collapsible-actions"
x-on:click.stop="isCollapsed = !isCollapsed">
<div class="fi-fo-builder-item-header-collapse-action">
{{ $getAction('collapse') }}
</div>
<div class="fi-fo-builder-item-header-expand-action">
{{ $getAction('expand') }}
</div>
</li>
@endif
</ul>
@endif
</div>
@endif
<div x-show="! isCollapsed" @class([
'fi-fo-builder-item-content',
'fi-fo-builder-item-content-has-preview' =>
$hasBlockPreviews && $item->getParentComponent()->hasPreview(),
])>
@if ($hasBlockPreviews && $item->getParentComponent()->hasPreview())
<div @class([
'fi-fo-builder-item-preview',
'fi-interactive' => $hasInteractiveBlockPreviews,
])>
{{ $item->getParentComponent()->renderPreview($item->getRawState()) }}
</div>
@if ($editActionIsVisible && !$hasInteractiveBlockPreviews)
<div class="fi-fo-builder-item-preview-edit-overlay" role="button"
x-on:click.stop="{{ '$wire.mountAction(\'edit\', { item: \'' . $itemKey . '\' }, { schemaComponent: \'' . $key . '\' })' }}">
</div>
@endif
@else
{{ $item }}
@endif
</div>
</li>
@if (!$loop->last)
@if ($isAddable && $addBetweenAction(['afterItem' => $itemKey])->isVisible())
<li class="fi-fo-builder-add-between-items-ctn">
<div class="fi-fo-builder-add-between-items">
<div class="fi-fo-builder-block-picker-ctn">
<x-filament-forms::builder.block-picker :action="$addBetweenAction" :after-item="$itemKey"
:columns="$blockPickerColumns" :blocks="$blockPickerBlocks" :key="$key" :width="$blockPickerWidth">
<x-slot name="trigger">
{{ $addBetweenAction(['afterItem' => $itemKey]) }}
</x-slot>
</x-filament-forms::builder.block-picker>
</div>
</div>
</li>
@elseif (filled($labelBetweenItems))
<li class="fi-fo-builder-label-between-items-ctn">
<div class="fi-fo-builder-label-between-items-divider-before"></div>
<span class="fi-fo-builder-label-between-items">
{{ $labelBetweenItems }}
</span>
<div class="fi-fo-builder-label-between-items-divider-after"></div>
</li>
@endif
@endif
@endforeach
</ul>
@endif
@if ($isAddable && $addAction->isVisible())
<x-filament-forms::builder.block-picker :action="$addAction" :action-alignment="$addActionAlignment" :blocks="$blockPickerBlocks"
:columns="$blockPickerColumns" :key="$key" :width="$blockPickerWidth">
<x-slot name="trigger">
{{ $addAction }}
</x-slot>
</x-filament-forms::builder.block-picker>
@endif
</div>
</x-dynamic-component>
@@ -0,0 +1,55 @@
@php
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\GridDirection;
use Illuminate\View\ComponentAttributeBag;
@endphp
@props([
'action',
'actionAlignment' => null,
'afterItem' => null,
'blocks',
'columns' => null,
'key',
'trigger',
'width' => null,
])
<x-filament::dropdown :placement="match ($actionAlignment) {
Alignment::Start, Alignment::Left => 'bottom-start',
Alignment::End, Alignment::Right => 'bottom-end',
default => null,
}" shift :width="$width" :attributes="\Filament\Support\prepare_inherited_attributes(
$attributes->class([
'fi-fo-builder-block-picker',
$actionAlignment instanceof Alignment ? 'fi-align-' . $actionAlignment->value : $actionAlignment,
]),
)">
<x-slot name="trigger">
{{ $trigger }}
</x-slot>
<x-filament::dropdown.list>
<div {{ new ComponentAttributeBag()->grid($columns, GridDirection::Column) }}>
@foreach ($blocks as $block)
@php
$blockIcon = $block->getIcon();
$wireClickActionArguments = ['block' => $block->getName()];
if (filled($afterItem)) {
$wireClickActionArguments['afterItem'] = $afterItem;
}
$wireClickActionArguments = \Illuminate\Support\Js::from($wireClickActionArguments);
$wireClickAction = "mountAction('{$action->getName()}', {$wireClickActionArguments}, { schemaComponent: '{$key}' })";
@endphp
<x-filament::dropdown.list.item :icon="$blockIcon" x-on:click="close" :wire:click="$wireClickAction">
{{ $block->getLabel() }}
</x-filament::dropdown.list.item>
@endforeach
</div>
</x-filament::dropdown.list>
</x-filament::dropdown>
@@ -0,0 +1,110 @@
@php
use Filament\Support\Enums\GridDirection;
$fieldWrapperView = $getFieldWrapperView();
$extraInputAttributeBag = $getExtraInputAttributeBag();
$isHtmlAllowed = $isHtmlAllowed();
$gridDirection = $getGridDirection() ?? GridDirection::Column;
$isBulkToggleable = $isBulkToggleable();
$isDisabled = $isDisabled();
$isSearchable = $isSearchable();
$statePath = $getStatePath();
$options = $getOptions();
$livewireKey = $getLivewireKey();
$wireModelAttribute = $applyStateBindingModifiers('wire:model');
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<div x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('checkbox-list', 'filament/forms') }}"
x-data="checkboxListFormComponent({
livewireId: @js($this->getId()),
})" {{ $getExtraAlpineAttributeBag()->class(['fi-fo-checkbox-list']) }}>
@if (!$isDisabled)
@if ($isSearchable)
<x-filament::input.wrapper inline-prefix :prefix-icon="\Filament\Support\Icons\Heroicon::MagnifyingGlass" :prefix-icon-alias="\Filament\Forms\View\FormsIconAlias::COMPONENTS_CHECKBOX_LIST_SEARCH_FIELD"
class="fi-fo-checkbox-list-search-input-wrp">
<input placeholder="{{ $getSearchPrompt() }}" type="search"
x-model.debounce.{{ $getSearchDebounce() }}="search"
class="fi-input fi-input-has-inline-prefix" />
</x-filament::input.wrapper>
@endif
@if ($isBulkToggleable && count($options))
<div x-cloak class="fi-fo-checkbox-list-actions" wire:key="{{ $livewireKey }}.actions">
<span x-show="! areAllCheckboxesChecked" x-on:click="toggleAllCheckboxes()"
wire:key="{{ $livewireKey }}.actions.select-all">
{{ $getAction('selectAll') }}
</span>
<span x-show="areAllCheckboxesChecked" x-on:click="toggleAllCheckboxes()"
wire:key="{{ $livewireKey }}.actions.deselect-all">
{{ $getAction('deselectAll') }}
</span>
</div>
@endif
@endif
<div
{{ $getExtraAttributeBag()->grid($getColumns(), $gridDirection)->merge(
[
'x-show' => $isSearchable ? 'visibleCheckboxListOptions.length' : null,
],
escape: false,
)->class(['fi-fo-checkbox-list-options']) }}>
@forelse ($options as $value => $label)
<div wire:key="{{ $livewireKey }}.options.{{ $value }}"
@if ($isSearchable) x-show="
$el
.querySelector('.fi-fo-checkbox-list-option-label')
?.innerText.toLowerCase()
.includes(search.toLowerCase()) ||
$el
.querySelector('.fi-fo-checkbox-list-option-description')
?.innerText.toLowerCase()
.includes(search.toLowerCase())
" @endif
class="fi-fo-checkbox-list-option-ctn">
<label class="fi-fo-checkbox-list-option">
<input type="checkbox"
{{ $extraInputAttributeBag->merge(
[
'disabled' => $isDisabled || $isOptionDisabled($value, $label),
'value' => e($value),
'wire:loading.attr' => 'disabled',
$wireModelAttribute => $statePath,
'x-on:change' => $isBulkToggleable ? 'checkIfAllCheckboxesAreChecked()' : null,
],
escape: false,
)->class(['fi-checkbox-input', 'fi-valid' => !$errors->has($statePath), 'fi-invalid' => $errors->has($statePath)]) }} />
<div class="fi-fo-checkbox-list-option-text">
<span class="fi-fo-checkbox-list-option-label">
@if ($isHtmlAllowed)
{!! $label !!}
@else
{{ $label }}
@endif
</span>
@if ($hasDescription($value))
<p class="fi-fo-checkbox-list-option-description">
{{ $getDescription($value) }}
</p>
@endif
</div>
</label>
</div>
@empty
<div wire:key="{{ $livewireKey }}.empty"></div>
@endforelse
</div>
@if ($isSearchable)
<div x-cloak x-show="search && ! visibleCheckboxListOptions.length"
class="fi-fo-checkbox-list-no-search-results-message">
{{ $getNoSearchResultsMessage() }}
</div>
@endif
</div>
</x-dynamic-component>
@@ -0,0 +1,33 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$statePath = $getStatePath();
$attributes = $attributes
->merge(
[
'autofocus' => $isAutofocused(),
'disabled' => $isDisabled(),
'id' => $getId(),
'required' => $isRequired() && !$isConcealed(),
'wire:loading.attr' => 'disabled',
$applyStateBindingModifiers('wire:model') => $statePath,
],
escape: false,
)
->merge($getExtraAttributes(), escape: false)
->merge($getExtraInputAttributes(), escape: false)
->class([
'fi-checkbox-input',
'fi-valid' => !$errors->has($statePath),
'fi-invalid' => $errors->has($statePath),
]);
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" :inline-label-vertical-alignment="\Filament\Support\Enums\VerticalAlignment::Center">
@if ($isInline())
<x-slot name="labelPrefix">
<input type="checkbox" {{ $attributes }} />
</x-slot>
@else
<input type="checkbox" {{ $attributes }} />
@endif
</x-dynamic-component>
@@ -0,0 +1,34 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$extraAttributeBag = $getExtraAttributeBag();
$isDisabled = $isDisabled();
$isLive = $isLive();
$isLiveOnBlur = $isLiveOnBlur();
$isLiveDebounced = $isLiveDebounced();
$liveDebounce = $getLiveDebounce();
$key = $getKey();
$language = $getLanguage();
$statePath = $getStatePath();
$livewireKey = $getLivewireKey();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<x-filament::input.wrapper :disabled="$isDisabled" :valid="!$errors->has($statePath)" :attributes="\Filament\Support\prepare_inherited_attributes($extraAttributeBag)->class(['fi-fo-code-editor'])">
<div x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('code-editor', 'filament/forms') }}"
x-data="codeEditorFormComponent({
canWrap: @js($canWrap()),
isDisabled: @js($isDisabled),
isLive: @js($isLive),
isLiveDebounced: @js($isLiveDebounced),
isLiveOnBlur: @js($isLiveOnBlur),
liveDebounce: @js($liveDebounce),
language: @js($language?->value),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')", isOptimisticallyLive: false) }},
})" wire:ignore
wire:key="{{ $livewireKey }}.{{ substr(md5(serialize([$isDisabled, $language?->value])), 0, 64) }}"
{{ $getExtraAlpineAttributeBag() }}>
<div x-ref="editor" x-cloak></div>
</div>
</x-filament::input.wrapper>
</x-dynamic-component>
@@ -0,0 +1,86 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$extraAttributeBag = $getExtraAttributeBag();
$isDisabled = $isDisabled();
$isLive = $isLive();
$isLiveOnBlur = $isLiveOnBlur();
$isLiveDebounced = $isLiveDebounced();
$isPrefixInline = $isPrefixInline();
$isSuffixInline = $isSuffixInline();
$liveDebounce = $getLiveDebounce();
$prefixActions = $getPrefixActions();
$prefixIcon = $getPrefixIcon();
$prefixIconColor = $getPrefixIconColor();
$prefixLabel = $getPrefixLabel();
$suffixActions = $getSuffixActions();
$suffixIcon = $getSuffixIcon();
$suffixIconColor = $getSuffixIconColor();
$suffixLabel = $getSuffixLabel();
$statePath = $getStatePath();
$placeholder = $getPlaceholder();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" :inline-label-vertical-alignment="\Filament\Support\Enums\VerticalAlignment::Center">
<x-filament::input.wrapper :disabled="$isDisabled" :inline-prefix="$isPrefixInline" :inline-suffix="$isSuffixInline" :prefix="$prefixLabel" :prefix-actions="$prefixActions"
:prefix-icon="$prefixIcon" :prefix-icon-color="$prefixIconColor" :suffix="$suffixLabel" :suffix-actions="$suffixActions" :suffix-icon="$suffixIcon" :suffix-icon-color="$suffixIconColor"
:valid="!$errors->has($statePath)" x-on:focus-input.stop="$el.querySelector('input')?.focus()" :attributes="\Filament\Support\prepare_inherited_attributes($extraAttributeBag)->class('fi-fo-color-picker')">
<div x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('color-picker', 'filament/forms') }}"
x-data="colorPickerFormComponent({
isAutofocused: @js($isAutofocused()),
isDisabled: @js($isDisabled),
isLive: @js($isLive),
isLiveDebounced: @js($isLiveDebounced),
isLiveOnBlur: @js($isLiveOnBlur),
liveDebounce: @js($liveDebounce),
state: $wire.$entangle('{{ $statePath }}'),
})" x-on:keydown.esc="isOpen() && $event.stopPropagation()"
x-on:focusout="if (isOpen() && ! $el.contains($event.relatedTarget)) $refs.panel.close()"
{{ $getExtraAlpineAttributeBag()->class(['fi-input-wrp-content']) }}>
<input x-on:focus="$refs.panel.open($refs.input)" x-on:keydown.enter.prevent.stop="togglePanelVisibility()"
x-ref="input"
{{ $getExtraInputAttributeBag()->merge(
[
'autocomplete' => 'off',
'disabled' => $isDisabled,
'id' => $getId(),
'placeholder' => filled($placeholder) ? e($placeholder) : null,
'required' => $isRequired() && !$isConcealed(),
'type' => 'text',
'x-model' . ($isLiveDebounced ? '.debounce.' . $liveDebounce : null) => 'state',
'x-on:blur' => $isLiveOnBlur ? 'isOpen() ? null : commitState()' : null,
],
escape: false,
)->class([
'fi-input',
'fi-input-has-inline-prefix' =>
$isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
'fi-input-has-inline-suffix' =>
$isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel)),
]) }} />
<div class="fi-fo-color-picker-preview my-auto me-3 size-5 shrink-0 rounded-full select-none"
x-on:click="togglePanelVisibility()"
x-bind:class="{
'fi-empty': !state,
}"
x-bind:style="{ 'background-color': state }"></div>
<div wire:ignore.self wire:key="{{ $getLivewireKey() }}.panel" x-cloak
x-float.placement.bottom-start.offset.flip.shift="{ offset: 8 }" x-ref="panel"
class="fi-fo-color-picker-panel">
@php
$tag =
match ($getFormat()) {
'hsl' => 'hsl-string',
'rgb' => 'rgb-string',
'rgba' => 'rgba-string',
default => 'hex',
} . '-color-picker';
@endphp
<{{ $tag }} x-ref="picker" color="{{ $getState() }}" />
</div>
</div>
</x-filament::input.wrapper>
</x-dynamic-component>
@@ -0,0 +1,192 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$datalistOptions = $getDatalistOptions();
$disabledDates = $getDisabledDates();
$extraAlpineAttributes = $getExtraAlpineAttributes();
$extraAttributeBag = $getExtraAttributeBag();
$extraInputAttributeBag = $getExtraInputAttributeBag();
$hasDate = $hasDate();
$hasTime = $hasTime();
$hasSeconds = $hasSeconds();
$id = $getId();
$isDisabled = $isDisabled();
$isAutofocused = $isAutofocused();
$isPrefixInline = $isPrefixInline();
$isSuffixInline = $isSuffixInline();
$maxDate = $getMaxDate();
$minDate = $getMinDate();
$defaultFocusedDate = $getDefaultFocusedDate();
$prefixActions = $getPrefixActions();
$prefixIcon = $getPrefixIcon();
$prefixIconColor = $getPrefixIconColor();
$prefixLabel = $getPrefixLabel();
$suffixActions = $getSuffixActions();
$suffixIcon = $getSuffixIcon();
$suffixIconColor = $getSuffixIconColor();
$suffixLabel = $getSuffixLabel();
$statePath = $getStatePath();
$placeholder = $getPlaceholder();
$isReadOnly = $isReadOnly();
$isRequired = $isRequired();
$isConcealed = $isConcealed();
$step = $getStep();
$type = $getType();
$livewireKey = $getLivewireKey();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" :inline-label-vertical-alignment="\Filament\Support\Enums\VerticalAlignment::Center">
<x-filament::input.wrapper :disabled="$isDisabled" :inline-prefix="$isPrefixInline" :inline-suffix="$isSuffixInline" :prefix="$prefixLabel" :prefix-actions="$prefixActions"
:prefix-icon="$prefixIcon" :prefix-icon-color="$prefixIconColor" :suffix="$suffixLabel" :suffix-actions="$suffixActions" :suffix-icon="$suffixIcon" :suffix-icon-color="$suffixIconColor"
:valid="!$errors->has($statePath)" x-on:focus-input.stop="$el.querySelector('input:not([type=hidden])')?.focus()"
:attributes="\Filament\Support\prepare_inherited_attributes($extraAttributeBag)->class([
'fi-fo-date-time-picker',
])">
@if ($isNative())
<input
{{ $extraInputAttributeBag->merge($extraAlpineAttributes, escape: false)->merge(
[
'autofocus' => $isAutofocused,
'disabled' => $isDisabled,
'id' => $id,
'list' => $datalistOptions ? $id . '-list' : null,
'max' => $hasTime ? $maxDate : ($maxDate ? \Carbon\Carbon::parse($maxDate)->toDateString() : null),
'min' => $hasTime ? $minDate : ($minDate ? \Carbon\Carbon::parse($minDate)->toDateString() : null),
'placeholder' => filled($placeholder) ? e($placeholder) : null,
'readonly' => $isReadOnly,
'required' => $isRequired && !$isConcealed,
'step' => $step,
'type' => $type,
$applyStateBindingModifiers('wire:model') => $statePath,
'x-data' => count($extraAlpineAttributes) ? '{}' : null,
],
escape: false,
)->class([
'fi-input',
'fi-input-has-inline-prefix' =>
$isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
'fi-input-has-inline-suffix' =>
$isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel)),
]) }} />
@else
<div x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('date-time-picker', 'filament/forms') }}"
x-data="dateTimePickerFormComponent({
defaultFocusedDate: @js($defaultFocusedDate),
displayFormat: '{{ convert_date_format($getDisplayFormat())->to('day.js') }}',
firstDayOfWeek: {{ $getFirstDayOfWeek() }},
isAutofocused: @js($isAutofocused),
locale: @js($getLocale()),
shouldCloseOnDateSelection: @js($shouldCloseOnDateSelection()),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
})" wire:ignore
wire:key="{{ $livewireKey }}.{{ substr(
md5(serialize([$disabledDates, $isDisabled, $isReadOnly, $maxDate, $minDate, $hasDate, $hasTime, $hasSeconds])),
0,
64,
) }}"
x-on:keydown.esc="isOpen() && $event.stopPropagation()" {{ $getExtraAlpineAttributeBag() }}>
<input x-ref="maxDate" type="hidden" value="{{ $maxDate }}" />
<input x-ref="minDate" type="hidden" value="{{ $minDate }}" />
<input x-ref="disabledDates" type="hidden" value="{{ json_encode($disabledDates) }}" />
<button x-ref="button" x-on:click="togglePanelVisibility()"
x-on:keydown.enter.prevent.stop="
if (! $el.disabled) {
isOpen() ? selectDate() : togglePanelVisibility()
}
"
x-on:keydown.arrow-left.prevent.stop="if (! $el.disabled) focusPreviousDay()"
x-on:keydown.arrow-right.prevent.stop="if (! $el.disabled) focusNextDay()"
x-on:keydown.arrow-up.prevent.stop="if (! $el.disabled) focusPreviousWeek()"
x-on:keydown.arrow-down.prevent.stop="if (! $el.disabled) focusNextWeek()"
x-on:keydown.backspace.prevent.stop="if (! $el.disabled) clearState()"
x-on:keydown.clear.prevent.stop="if (! $el.disabled) clearState()"
x-on:keydown.delete.prevent.stop="if (! $el.disabled) clearState()"
aria-label="{{ $placeholder }}" type="button" tabindex="-1" @disabled($isDisabled || $isReadOnly)
{{ $getExtraTriggerAttributeBag()->class(['fi-fo-date-time-picker-trigger']) }}>
<input @disabled($isDisabled) readonly placeholder="{{ $placeholder }}"
wire:key="{{ $livewireKey }}.display-text" x-model="displayText"
@if ($id = $getId()) id="{{ $id }}" @endif
@class(['fi-fo-date-time-picker-display-text-input']) />
</button>
<div x-ref="panel" x-cloak x-float.placement.bottom-start.offset.flip.shift="{ offset: 8 }" wire:ignore
wire:key="{{ $livewireKey }}.panel" @class(['fi-fo-date-time-picker-panel'])>
@if ($hasDate)
<div class="fi-fo-date-time-picker-panel-header">
<select x-model="focusedMonth" class="fi-fo-date-time-picker-month-select">
<template x-for="(month, index) in months">
<option x-bind:value="index" x-text="month"></option>
</template>
</select>
<input type="number" inputmode="numeric" x-model.debounce="focusedYear"
class="fi-fo-date-time-picker-year-input" />
</div>
<div class="fi-fo-date-time-picker-calendar-header">
<template x-for="(day, index) in dayLabels" x-bind:key="index">
<div x-text="day" class="fi-fo-date-time-picker-calendar-header-day"></div>
</template>
</div>
<div role="grid" class="fi-fo-date-time-picker-calendar">
<template x-for="day in emptyDaysInFocusedMonth" x-bind:key="day">
<div></div>
</template>
<template x-for="day in daysInFocusedMonth" x-bind:key="day">
<div x-text="day" x-on:click="dayIsDisabled(day) || selectDate(day)"
x-on:mouseenter="setFocusedDay(day)" role="option"
x-bind:aria-selected="focusedDate.date() === day"
x-bind:class="{
'fi-fo-date-time-picker-calendar-day-today': dayIsToday(day),
'fi-focused': focusedDate.date() === day,
'fi-selected': dayIsSelected(day),
'fi-disabled': dayIsDisabled(day),
}"
class="fi-fo-date-time-picker-calendar-day"></div>
</template>
</div>
@endif
@if ($hasTime)
<div class="fi-fo-date-time-picker-time-inputs">
<input max="23" min="0" step="{{ $getHoursStep() }}" type="number"
inputmode="numeric" x-on:blur="checkTimeInputValidity" x-on:invalid="timeInputInvalid"
x-model.debounce="hour" />
<span class="fi-fo-date-time-picker-time-input-separator">
:
</span>
<input max="59" min="0" step="{{ $getMinutesStep() }}" type="number"
inputmode="numeric" x-on:blur="checkTimeInputValidity" x-on:invalid="timeInputInvalid"
x-model.debounce="minute" />
@if ($hasSeconds)
<span class="fi-fo-date-time-picker-time-input-separator">
:
</span>
<input max="59" min="0" step="{{ $getSecondsStep() }}" type="number"
inputmode="numeric" x-on:blur="checkTimeInputValidity"
x-on:invalid="timeInputInvalid" x-model.debounce="second" />
@endif
</div>
@endif
</div>
</div>
@endif
</x-filament::input.wrapper>
@if ($datalistOptions)
<datalist id="{{ $id }}-list">
@foreach ($datalistOptions as $option)
<option value="{{ $option }}" />
@endforeach
</datalist>
@endif
</x-dynamic-component>
@@ -0,0 +1,202 @@
@php
use Filament\Support\Enums\VerticalAlignment;
@endphp
@props([
'areHtmlErrorMessagesAllowed' => null,
'errorMessage' => null,
'errorMessages' => null,
'field' => null,
'hasErrors' => true,
'hasInlineLabel' => null,
'hasNestedRecursiveValidationRules' => null,
'id' => null,
'inlineLabelVerticalAlignment' => VerticalAlignment::Start,
'isDisabled' => null,
'label' => null,
'labelPrefix' => null,
'labelSrOnly' => null,
'labelSuffix' => null,
'labelTag' => 'label',
'required' => null,
'shouldShowAllValidationMessages' => null,
'statePath' => null,
])
@php
use Illuminate\Support\Arr;
if ($field) {
$hasInlineLabel ??= $field->hasInlineLabel();
$hasNestedRecursiveValidationRules ??=
$field instanceof \Filament\Forms\Components\Contracts\HasNestedRecursiveValidationRules;
$id ??= $field->getId();
$isDisabled ??= $field->isDisabled();
$label ??= $field->getLabel();
$labelSrOnly ??= $field->isLabelHidden();
$required ??= $field->isMarkedAsRequired();
$statePath ??= $field->getStatePath();
$areHtmlErrorMessagesAllowed ??= $field->areHtmlValidationMessagesAllowed();
$shouldShowAllValidationMessages ??= $field->shouldShowAllValidationMessages();
}
$aboveLabelSchema = $field?->getChildSchema($field::ABOVE_LABEL_SCHEMA_KEY)?->toHtmlString();
$belowLabelSchema = $field?->getChildSchema($field::BELOW_LABEL_SCHEMA_KEY)?->toHtmlString();
$beforeLabelSchema = $field?->getChildSchema($field::BEFORE_LABEL_SCHEMA_KEY)?->toHtmlString();
$afterLabelSchema = $field?->getChildSchema($field::AFTER_LABEL_SCHEMA_KEY)?->toHtmlString();
$aboveContentSchema = $field?->getChildSchema($field::ABOVE_CONTENT_SCHEMA_KEY)?->toHtmlString();
$belowContentSchema = $field?->getChildSchema($field::BELOW_CONTENT_SCHEMA_KEY)?->toHtmlString();
$beforeContentSchema = $field?->getChildSchema($field::BEFORE_CONTENT_SCHEMA_KEY)?->toHtmlString();
$afterContentSchema = $field?->getChildSchema($field::AFTER_CONTENT_SCHEMA_KEY)?->toHtmlString();
$aboveErrorMessageSchema = $field?->getChildSchema($field::ABOVE_ERROR_MESSAGE_SCHEMA_KEY)?->toHtmlString();
$belowErrorMessageSchema = $field?->getChildSchema($field::BELOW_ERROR_MESSAGE_SCHEMA_KEY)?->toHtmlString();
$hasError =
$hasErrors &&
(filled($errorMessage) ||
filled($errorMessages) ||
(filled($statePath) &&
($errors->has($statePath) || ($hasNestedRecursiveValidationRules && $errors->has("{$statePath}.*")))));
if ($hasError && filled($statePath) && blank($errorMessage) && blank($errorMessages)) {
if ($shouldShowAllValidationMessages) {
$errorMessages = $errors->has($statePath)
? $errors->get($statePath)
: ($hasNestedRecursiveValidationRules
? $errors->get("{$statePath}.*")
: []);
if (count($errorMessages) === 1) {
$errorMessage = Arr::first($errorMessages);
$errorMessages = [];
}
} else {
$errorMessage = $errors->has($statePath)
? $errors->first($statePath)
: ($hasNestedRecursiveValidationRules
? $errors->first("{$statePath}.*")
: null);
}
}
@endphp
<div data-field-wrapper
{{ $attributes->merge($field?->getExtraFieldWrapperAttributes() ?? [], escape: false)->class(['fi-fo-field', 'fi-fo-field-has-inline-label' => $hasInlineLabel]) }}>
@if (filled($label) && $labelSrOnly)
<{{ $labelTag }}
@if ($labelTag === 'label') for="{{ $id }}"
@else
id="{{ $id }}-label" @endif
class="fi-fo-field-label fi-sr-only">
{{ $label }}
</{{ $labelTag }}>
@endif
@if (
(filled($label) && !$labelSrOnly) ||
$hasInlineLabel ||
$aboveLabelSchema ||
$belowLabelSchema ||
$beforeLabelSchema ||
$afterLabelSchema ||
$labelPrefix ||
$labelSuffix)
<div @class([
'fi-fo-field-label-col',
"fi-vertical-align-{$inlineLabelVerticalAlignment->value}" => $hasInlineLabel,
])>
{{ $aboveLabelSchema }}
<div @class([
'fi-fo-field-label-ctn',
$label instanceof \Illuminate\View\ComponentSlot
? $label->attributes->get('class')
: null,
])>
{{ $beforeLabelSchema }}
@if ((filled($label) && !$labelSrOnly) || $labelPrefix || $labelSuffix)
<{{ $labelTag }}
@if ($labelTag === 'label') for="{{ $id }}"
@else
id="{{ $id }}-label" @endif
class="fi-fo-field-label">
{{ $labelPrefix }}
@if (filled($label) && !$labelSrOnly)
<span class="fi-fo-field-label-content">
{{ $label }}@if ($required && !$isDisabled)
<sup class="fi-fo-field-label-required-mark">*</sup>
@endif
</span>
@endif
{{ $labelSuffix }}
</{{ $labelTag }}>
@endif
{{ $afterLabelSchema }}
</div>
{{ $belowLabelSchema }}
</div>
@endif
@if (
!\Filament\Support\is_slot_empty($slot) ||
$hasError ||
$aboveContentSchema ||
$belowContentSchema ||
$beforeContentSchema ||
$afterContentSchema ||
$aboveErrorMessageSchema ||
$belowErrorMessageSchema)
<div class="fi-fo-field-content-col">
{{ $aboveContentSchema }}
@if ($beforeContentSchema || $afterContentSchema)
<div class="fi-fo-field-content-ctn">
{{ $beforeContentSchema }}
<div class="fi-fo-field-content">
{{ $slot }}
</div>
{{ $afterContentSchema }}
</div>
@else
{{ $slot }}
@endif
{{ $belowContentSchema }}
@if ($hasError)
{{ $aboveErrorMessageSchema }}
@if (filled($errorMessages))
<ul data-validation-error class="fi-fo-field-wrp-error-list">
@foreach ($errorMessages as $errorMessage)
<li class="fi-fo-field-wrp-error-message">
@if ($areHtmlErrorMessagesAllowed)
{!! $errorMessage !!}
@else
{{ $errorMessage }}
@endif
</li>
@endforeach
</ul>
@elseif ($areHtmlErrorMessagesAllowed)
<div data-validation-error class="fi-fo-field-wrp-error-message">
{!! $errorMessage !!}
</div>
@else
<p data-validation-error class="fi-fo-field-wrp-error-message">
{{ $errorMessage }}
</p>
@endif
{{ $belowErrorMessageSchema }}
@endif
</div>
@endif
</div>
@@ -0,0 +1,292 @@
@php
use Filament\Support\Enums\Alignment;
$fieldWrapperView = $getFieldWrapperView();
$id = $getId();
$automaticallyCropImagesAspectRatio = $getAutomaticallyCropImagesAspectRatio();
$automaticallyResizeImagesHeight = $getAutomaticallyResizeImagesHeight();
$automaticallyResizeImagesWidth = $getAutomaticallyResizeImagesWidth();
$isAvatar = $isAvatar();
$isMultiple = $isMultiple();
$key = $getKey();
$statePath = $getStatePath();
$isDisabled = $isDisabled();
$hasImageEditor = $hasImageEditor();
$isImageEditorExplicitlyEnabled = $isImageEditorExplicitlyEnabled();
$hasCircleCropper = $hasCircleCropper();
$livewireKey = $getLivewireKey();
$alignment = $getAlignment() ?? Alignment::Start;
if (!$alignment instanceof Alignment) {
$alignment = filled($alignment) ? Alignment::tryFrom($alignment) ?? $alignment : null;
}
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" label-tag="div">
<div x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('file-upload', 'filament/forms') }}"
x-data="fileUploadFormComponent({
acceptedFileTypes: @js($getAcceptedFileTypes()),
automaticallyCropImagesAspectRatio: @js($automaticallyCropImagesAspectRatio),
automaticallyOpenImageEditorForAspectRatio: @js($getAutomaticallyOpenImageEditorForAspectRatio()),
automaticallyResizeImagesMode: @js($getAutomaticallyResizeImagesMode()),
automaticallyResizeImagesHeight: @js($automaticallyResizeImagesHeight),
automaticallyResizeImagesWidth: @js($automaticallyResizeImagesWidth),
cancelUploadUsing: (fileKey) => {
$wire.cancelUpload(`{{ $statePath }}.${fileKey}`)
},
canEditSvgs: @js($canEditSvgs()),
confirmSvgEditingMessage: @js(__('filament-forms::components.file_upload.editor.svg.messages.confirmation')),
deleteUploadedFileUsing: async (fileKey) => {
return await $wire.callSchemaComponentMethod(
@js($key),
'deleteUploadedFile', { fileKey },
)
},
disabledSvgEditingMessage: @js(__('filament-forms::components.file_upload.editor.svg.messages.disabled')),
getUploadedFilesUsing: async () => {
return await Livewire.fireAction(
$wire.__instance,
'callSchemaComponentMethod',
[@js($key), 'getUploadedFiles'], { async: true },
)
},
hasCircleCropper: @js($hasCircleCropper),
hasImageEditor: @js($hasImageEditor),
imageEditorEmptyFillColor: @js($getImageEditorEmptyFillColor()),
imageEditorMode: @js($getImageEditorMode()),
imageEditorViewportHeight: @js($getImageEditorViewportHeight()),
imageEditorViewportWidth: @js($getImageEditorViewportWidth()),
imagePreviewHeight: @js($getImagePreviewHeight()),
isAvatar: @js($isAvatar),
isDeletable: @js($isDeletable()),
isDisabled: @js($isDisabled),
isDownloadable: @js($isDownloadable()),
isImageEditorExplicitlyEnabled: @js($isImageEditorExplicitlyEnabled),
isMultiple: @js($isMultiple),
isOpenable: @js($isOpenable()),
isPasteable: @js($isPasteable()),
isPreviewable: @js($isPreviewable()),
isReorderable: @js($isReorderable()),
isSvgEditingConfirmed: @js($isSvgEditingConfirmed()),
itemPanelAspectRatio: @js($getItemPanelAspectRatio()),
loadingIndicatorPosition: @js($getLoadingIndicatorPosition()),
locale: @js(app()->getLocale()),
maxFiles: @js($maxFiles = $getMaxFiles()),
maxFilesValidationMessage: @js($maxFiles ? trans_choice('validation.max.array', $maxFiles, ['attribute' => $getValidationAttribute(), 'max' => $maxFiles]) : null),
maxParallelUploads: @js($getMaxParallelUploads()),
maxSize: @js(($size = $getMaxSize()) ? "{$size}KB" : null),
mimeTypeMap: @js($getMimeTypeMap()),
minSize: @js(($size = $getMinSize()) ? "{$size}KB" : null),
panelAspectRatio: @js($getPanelAspectRatio()),
panelLayout: @js($getPanelLayout()),
placeholder: @js($getPlaceholder()),
removeUploadedFileButtonPosition: @js($getRemoveUploadedFileButtonPosition()),
removeUploadedFileUsing: async (fileKey) => {
return await $wire.callSchemaComponentMethod(
@js($key),
'removeUploadedFile', { fileKey },
)
},
reorderUploadedFilesUsing: async (fileKeys) => {
return await $wire.callSchemaComponentMethod(
@js($key),
'reorderUploadedFiles', { fileKeys },
)
},
shouldAppendFiles: @js($shouldAppendFiles()),
shouldAutomaticallyUpscaleImagesWhenResizing: @js($shouldAutomaticallyUpscaleImagesWhenResizing()),
shouldOrientImageFromExif: @js($shouldOrientImagesFromExif()),
shouldTransformImage: @js($automaticallyCropImagesAspectRatio || $automaticallyResizeImagesHeight || $automaticallyResizeImagesWidth),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
uploadButtonPosition: @js($getUploadButtonPosition()),
uploadingMessage: @js($getUploadingMessage()),
uploadProgressIndicatorPosition: @js($getUploadProgressIndicatorPosition()),
uploadUsing: (fileKey, file, success, error, progress) => {
$wire.upload(
`{{ $statePath }}.${fileKey}`,
file,
() => {
success(fileKey)
},
error,
(progressEvent) => {
progress(true, progressEvent.detail.progress, 100)
},
)
},
})" wire:ignore
wire:key="{{ $livewireKey }}.{{ substr(md5(serialize([$isDisabled])), 0, 64) }}"
{{ $attributes->merge(
[
'aria-labelledby' => "{$id}-label",
'id' => $id,
'role' => 'group',
],
escape: false,
)->merge($getExtraAttributes(), escape: false)->merge($getExtraAlpineAttributes(), escape: false)->class([
'fi-fo-file-upload',
'fi-fo-file-upload-avatar' => $isAvatar,
$alignment instanceof Alignment ? "fi-align-{$alignment->value}" : $alignment,
]) }}>
<div class="fi-fo-file-upload-input-ctn">
<input x-ref="input"
{{ $getExtraInputAttributeBag()->merge(
[
'aria-labelledby' => "{$id}-label",
'disabled' => $isDisabled,
'multiple' => $isMultiple,
'type' => 'file',
],
escape: false,
) }} />
</div>
<div x-show="error" x-text="error" x-cloak class="fi-fo-file-upload-error-message"></div>
@if ($hasImageEditor && !$isDisabled)
<div x-show="isEditorOpen" x-cloak x-on:click.stop="" x-trap.noscroll="isEditorOpen"
x-on:keydown.escape.prevent.stop="closeEditor" @class([
'fi-fo-file-upload-editor',
'fi-fo-file-upload-editor-circle-cropper' => $hasCircleCropper,
'fi-fo-file-upload-editor-crop-only' => !$isImageEditorExplicitlyEnabled,
])>
<div aria-hidden="true" class="fi-fo-file-upload-editor-overlay"></div>
<div class="fi-fo-file-upload-editor-window">
<div class="fi-fo-file-upload-editor-image-ctn">
<img x-ref="editor" class="fi-fo-file-upload-editor-image" />
</div>
<div class="fi-fo-file-upload-editor-control-panel">
@if ($isImageEditorExplicitlyEnabled)
<div class="fi-fo-file-upload-editor-control-panel-main">
<div class="fi-fo-file-upload-editor-control-panel-group">
@foreach ([
[
'label' => __('filament-forms::components.file_upload.editor.fields.x_position.label'),
'ref' => 'xPositionInput',
'unit' => __('filament-forms::components.file_upload.editor.fields.x_position.unit'),
'alpineSaveHandler' => 'editor.setData({...editor.getData(true), x: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.editor.fields.y_position.label'),
'ref' => 'yPositionInput',
'unit' => __('filament-forms::components.file_upload.editor.fields.y_position.unit'),
'alpineSaveHandler' => 'editor.setData({...editor.getData(true), y: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.editor.fields.width.label'),
'ref' => 'widthInput',
'unit' => __('filament-forms::components.file_upload.editor.fields.width.unit'),
'alpineSaveHandler' => 'editor.setData({...editor.getData(true), width: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.editor.fields.height.label'),
'ref' => 'heightInput',
'unit' => __('filament-forms::components.file_upload.editor.fields.height.unit'),
'alpineSaveHandler' => 'editor.setData({...editor.getData(true), height: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.editor.fields.rotation.label'),
'ref' => 'rotationInput',
'unit' => __('filament-forms::components.file_upload.editor.fields.rotation.unit'),
'alpineSaveHandler' => 'editor.rotateTo(+$el.value)',
],
] as $input)
<label>
<x-filament::input.wrapper>
<x-slot name="prefix">
{{ $input['label'] }}
</x-slot>
<input x-on:keyup.enter.prevent.stop="editor && {!! $input['alpineSaveHandler'] !!}"
x-on:blur="editor && {!! $input['alpineSaveHandler'] !!}"
x-ref="{{ $input['ref'] }}" x-on:keydown.enter.prevent
type="text" class="fi-input" />
<x-slot name="suffix">
{{ $input['unit'] }}
</x-slot>
</x-filament::input.wrapper>
</label>
@endforeach
</div>
<div class="fi-fo-file-upload-editor-control-panel-group">
@foreach ($getImageEditorActions() as $groupedActions)
<div class="fi-btn-group">
@foreach ($groupedActions as $action)
<button aria-label="{{ $action['label'] }}" type="button"
x-on:click.prevent.stop="{{ $action['alpineClickHandler'] }}"
x-tooltip="{ content: @js($action['label']), theme: $store.theme }"
class="fi-btn">
{{ $action['iconHtml'] }}
</button>
@endforeach
</div>
@endforeach
</div>
@if (count($aspectRatios = $getImageEditorAspectRatioOptionsForJs()))
<div class="fi-fo-file-upload-editor-control-panel-group">
<div class="fi-fo-file-upload-editor-control-panel-group-title">
{{ __('filament-forms::components.file_upload.editor.aspect_ratios.label') }}
</div>
@foreach (collect($aspectRatios)->chunk(5) as $ratiosChunk)
<div class="fi-btn-group">
@foreach ($ratiosChunk as $label => $ratio)
<button type="button"
x-on:click.prevent.stop="
currentRatio = @js($label) {!! ';' !!}
editor.setAspectRatio(@js($ratio))
"
x-tooltip="{ content: @js(__('filament-forms::components.file_upload.editor.actions.set_aspect_ratio.label', ['ratio' => $label])), theme: $store.theme }"
x-bind:class="{ 'fi-active': currentRatio === @js($label) }"
class="fi-btn">
{{ $label }}
</button>
@endforeach
</div>
@endforeach
</div>
@endif
</div>
@endif
<div class="fi-fo-file-upload-editor-control-panel-footer">
@if ($isImageEditorExplicitlyEnabled)
<button type="button" x-on:click.prevent="pond.imageEditEditor.oncancel"
class="fi-btn">
{{ __('filament-forms::components.file_upload.editor.actions.cancel.label') }}
</button>
<button type="button" x-on:click.prevent.stop="editor.reset()"
{{ new \Illuminate\View\ComponentAttributeBag()->color(\Filament\Support\View\Components\ButtonComponent::class, 'danger')->class(['fi-btn fi-fo-file-upload-editor-control-panel-reset-action']) }}>
{{ __('filament-forms::components.file_upload.editor.actions.reset.label') }}
</button>
<button type="button" x-on:click.prevent="saveEditor"
{{ new \Illuminate\View\ComponentAttributeBag()->color(\Filament\Support\View\Components\ButtonComponent::class, 'success')->class(['fi-btn']) }}>
{{ __('filament-forms::components.file_upload.editor.actions.save.label') }}
</button>
@else
<button type="button" x-on:click.prevent="saveEditor"
{{ new \Illuminate\View\ComponentAttributeBag()->color(\Filament\Support\View\Components\ButtonComponent::class, 'success')->class(['fi-btn']) }}>
{{ __('filament-forms::components.file_upload.editor.actions.save.label') }}
</button>
<button type="button" x-on:click.prevent="pond.imageEditEditor.oncancel"
class="fi-btn">
{{ __('filament-forms::components.file_upload.editor.actions.cancel.label') }}
</button>
@endif
</div>
</div>
</div>
</div>
@endif
</div>
</x-dynamic-component>
@@ -0,0 +1,9 @@
<input
{{ $attributes->merge(
[
'id' => $getId(),
'type' => 'hidden',
$applyStateBindingModifiers('wire:model') => $getStatePath(),
],
escape: false,
)->merge($getExtraAttributes(), escape: false)->class(['fi-fo-hidden']) }} />
@@ -0,0 +1,92 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$extraAttributeBag = $getExtraAttributeBag();
$canEditKeys = $canEditKeys();
$canEditValues = $canEditValues();
$keyPlaceholder = $getKeyPlaceholder();
$valuePlaceholder = $getValuePlaceholder();
$debounce = $getLiveDebounce();
$isAddable = $isAddable();
$isDeletable = $isDeletable();
$isDisabled = $isDisabled();
$isReorderable = $isReorderable();
$statePath = $getStatePath();
$livewireKey = $getLivewireKey();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" class="fi-fo-key-value-wrp">
<x-filament::input.wrapper :disabled="$isDisabled" :valid="!$errors->has($statePath)" :attributes="\Filament\Support\prepare_inherited_attributes($extraAttributeBag)->class(['fi-fo-key-value'])">
<div x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('key-value', 'filament/forms') }}"
x-data="keyValueFormComponent({
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
})" wire:ignore
wire:key="{{ $livewireKey }}.{{ substr(md5(serialize([$isDisabled])), 0, 64) }}"
{{ $attributes->merge($getExtraAlpineAttributes(), escape: false)->class(['fi-fo-key-value-table-ctn']) }}>
<table class="fi-fo-key-value-table">
<thead>
<tr>
@if ($isReorderable && !$isDisabled)
<th scope="col" x-show="rows.length" class="fi-has-action"></th>
@endif
<th scope="col">
{{ $getKeyLabel() }}
</th>
<th scope="col">
{{ $getValueLabel() }}
</th>
@if ($isDeletable && !$isDisabled)
<th scope="col" x-show="rows.length" class="fi-has-action"></th>
@endif
</tr>
</thead>
<tbody
@if ($isReorderable) x-on:end.stop="reorderRows($event)"
x-sortable
data-sortable-animation-duration="{{ $getReorderAnimationDuration() }}" @endif>
<template x-bind:key="index" x-for="(row, index) in rows">
<tr @if ($isReorderable) x-bind:x-sortable-item="row.key" @endif>
@if ($isReorderable && !$isDisabled)
<td class="fi-has-action">
<div x-sortable-handle class="fi-fo-key-value-table-row-sortable-handle">
{{ $getAction('reorder') }}
</div>
</td>
@endif
<td>
<input @disabled(!$canEditKeys || $isDisabled) placeholder="{{ $keyPlaceholder }}" type="text"
x-model="row.key" x-on:input.debounce.{{ $debounce ?? '500ms' }}="updateState"
class="fi-input" />
</td>
<td>
<input @disabled(!$canEditValues || $isDisabled) placeholder="{{ $valuePlaceholder }}"
type="text" x-model="row.value"
x-on:input.debounce.{{ $debounce ?? '500ms' }}="updateState" class="fi-input" />
</td>
@if ($isDeletable && !$isDisabled)
<td class="fi-has-action">
<div x-on:click="deleteRow(index)">
{{ $getAction('delete') }}
</div>
</td>
@endif
</tr>
</template>
</tbody>
</table>
@if ($isAddable && !$isDisabled)
<div x-on:click="addRow" class="fi-fo-key-value-add-action-ctn">
{{ $getAction('add') }}
</div>
@endif
</div>
</x-filament::input.wrapper>
</x-dynamic-component>
@@ -0,0 +1,29 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$extraAttributes = $getExtraAttributes();
$id = $getId();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
@if (filled($id) || filled($extraAttributes))
{!! '<div' !!}
{{-- Avoid formatting issues with unclosed elements --}}
{{ $attributes->merge(
[
'id' => $id,
],
escape: false,
)->merge($extraAttributes, escape: false) }}
>
@endif
@if (filled($key = $getLivewireKey()))
@livewire($getComponent(), $getComponentProperties(), key($key))
@else
@livewire($getComponent(), $getComponentProperties())
@endif
@if (filled($id) || filled($extraAttributes))
{!! '</div>' !!}
{{-- Avoid formatting issues with unclosed elements --}}
@endif
</x-dynamic-component>
@@ -0,0 +1,66 @@
@php
$id = $getId();
$fieldWrapperView = $getFieldWrapperView();
$extraAttributeBag = $getExtraAttributeBag();
$key = $getKey();
$label = $getLabel();
$statePath = $getStatePath();
$fileAttachmentsMaxSize = $getFileAttachmentsMaxSize();
$fileAttachmentsAcceptedFileTypes = $getFileAttachmentsAcceptedFileTypes();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
@if ($isDisabled())
<div id="{{ $id }}" class="fi-fo-markdown-editor fi-disabled fi-prose">
{!! str($getState())->markdown($getCommonMarkOptions(), $getCommonMarkExtensions())->sanitizeHtml() !!}
</div>
@else
<x-filament::input.wrapper :valid="!$errors->has($statePath)" :attributes="\Filament\Support\prepare_inherited_attributes($extraAttributeBag)->class(['fi-fo-markdown-editor'])">
<div aria-labelledby="{{ $id }}-label" id="{{ $id }}" role="group" x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('markdown-editor', 'filament/forms') }}"
x-data="markdownEditorFormComponent({
canAttachFiles: @js($hasFileAttachments()),
isLiveDebounced: @js($isLiveDebounced()),
isLiveOnBlur: @js($isLiveOnBlur()),
label: @js($label),
liveDebounce: @js($getNormalizedLiveDebounce()),
maxHeight: @js($getMaxHeight()),
minHeight: @js($getMinHeight()),
placeholder: @js($getPlaceholder()),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')", isOptimisticallyLive: false) }},
toolbarButtons: @js($getToolbarButtons()),
translations: @js(__('filament-forms::components.markdown_editor')),
uploadFileAttachmentUsing: async (file, onSuccess, onError) => {
const acceptedTypes = @js($fileAttachmentsAcceptedFileTypes)
if (acceptedTypes && !acceptedTypes.includes(file.type)) {
return onError(@js($fileAttachmentsAcceptedFileTypes ? __('filament-forms::components.markdown_editor.file_attachments_accepted_file_types_message', ['values' => implode(', ', $fileAttachmentsAcceptedFileTypes)]) : null))
}
const maxSize = @js($fileAttachmentsMaxSize)
if (maxSize && file.size > +maxSize * 1024) {
return onError(@js($fileAttachmentsMaxSize ? trans_choice('filament-forms::components.markdown_editor.file_attachments_max_size_message', $fileAttachmentsMaxSize, ['max' => $fileAttachmentsMaxSize]) : null))
}
$wire.upload(`componentFileAttachments.{{ $statePath }}`, file, () => {
$wire
.callSchemaComponentMethod(
'{{ $key }}',
'saveUploadedFileAttachmentAndGetUrl',
)
.then((url) => {
if (!url) {
return onError()
}
onSuccess(url)
})
})
},
})" wire:ignore {{ $getExtraAlpineAttributeBag() }}>
<textarea x-ref="editor" x-cloak></textarea>
</div>
</x-filament::input.wrapper>
@endif
</x-dynamic-component>
@@ -0,0 +1,67 @@
@php
use Filament\Forms\Components\TableSelect\Livewire\TableSelectLivewireComponent;
$fieldWrapperView = $getFieldWrapperView();
$extraAttributes = $getExtraAttributes();
$id = $getId();
$isDisabled = $isDisabled();
$isMultiple = $isMultiple();
$hasBadges = $hasBadges();
$badgeColor = $getBadgeColor();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<div
{{ $attributes->merge(
[
'id' => $id,
],
escape: false,
)->merge($extraAttributes, escape: false)->class([
'fi-fo-modal-table-select',
'fi-fo-modal-table-select-disabled' => $isDisabled,
'fi-fo-modal-table-select-multiple' => $isMultiple,
]) }}>
@if (
(!$isMultiple && filled($optionLabel = $getOptionLabel())) ||
($isMultiple && filled($optionLabels = $getOptionLabels())))
@if ($isMultiple && $hasBadges)
<div class="fi-fo-modal-table-select-badges-ctn">
@foreach ($optionLabels as $optionLabel)
@if ($hasBadges)
<x-filament::badge :color="$badgeColor">
{{ $optionLabel }}
</x-filament::badge>
@else
{{ $optionLabel }}
@endif
@endforeach
</div>
@else
<div>
@if ($hasBadges)
<x-filament::badge :color="$badgeColor">
{{ $optionLabel }}
</x-filament::badge>
@elseif ($isMultiple)
@foreach ($optionLabels as $optionLabel)
{{ $optionLabel . ($loop->last ? '' : ', ') }}
@endforeach
@else
{{ $optionLabel }}
@endif
</div>
@endif
@elseif (filled($placeholder = $getPlaceholder()))
<div class="fi-fo-modal-table-select-placeholder">
{{ $placeholder }}
</div>
@endif
@if (!$isDisabled)
<div>
{{ $getAction('select') }}
</div>
@endif
</div>
</x-dynamic-component>
@@ -0,0 +1,25 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$placeholder = $getPlaceholder();
$extraAttributes = $getExtraAttributeBag()->merge($getExtraAlpineAttributes(), escape: false);
$extraInputAttributes = $getExtraInputAttributeBag()->merge(
[
'autocomplete' => false,
'autofocus' => $isAutofocused(),
'disabled' => $isDisabled(),
'id' => $getId(),
'length' => $getLength(),
'placeholder' => filled($placeholder) ? e($placeholder) : null,
'readonly' => $isReadOnly(),
'required' => $isRequired() && !$isConcealed(),
$applyStateBindingModifiers('wire:model') => $getStatePath(),
],
escape: false,
);
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<x-filament::input.one-time-code :attributes="\Filament\Support\prepare_inherited_attributes($extraAttributes)">
<x-slot name="input" :attributes="\Filament\Support\prepare_inherited_attributes($extraInputAttributes)"></x-slot>
</x-filament::input.one-time-code>
</x-dynamic-component>
@@ -0,0 +1,30 @@
@props([
'field' => null,
'id' => null,
'label' => null,
'labelTag' => 'label',
])
@php
use Illuminate\View\ComponentAttributeBag;
if ($field) {
$id ??= $field->getId();
$label ??= $field->getLabel();
}
@endphp
<div data-field-wrapper
{{ new ComponentAttributeBag()->merge($field?->getExtraFieldWrapperAttributes() ?? [], escape: false)->class(['fi-fo-field']) }}>
@if (filled($label))
<{{ $labelTag }}
@if ($labelTag === 'label') for="{{ $id }}"
@else
id="{{ $id }}-label" @endif
class="fi-fo-field-label fi-sr-only">
{{ $label }}
</{{ $labelTag }}>
@endif
{{ $slot }}
</div>
@@ -0,0 +1,55 @@
@php
use Filament\Support\Enums\GridDirection;
use Illuminate\View\ComponentAttributeBag;
$fieldWrapperView = $getFieldWrapperView();
$extraInputAttributeBag = $getExtraInputAttributeBag();
$gridDirection = $getGridDirection() ?? GridDirection::Column;
$id = $getId();
$isDisabled = $isDisabled();
$isInline = $isInline();
$statePath = $getStatePath();
$wireModelAttribute = $applyStateBindingModifiers('wire:model');
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<div
{{ $getExtraAttributeBag()->when(!$isInline, fn(ComponentAttributeBag $attributes) => $attributes->grid($getColumns(), $gridDirection))->class(['fi-fo-radio', 'fi-inline' => $isInline]) }}>
@foreach ($getOptions() as $value => $label)
@php
$inputAttributes = $extraInputAttributeBag->merge(
[
'autofocus' => $loop->first && $isAutofocused(),
'disabled' => $isDisabled || $isOptionDisabled($value, $label),
'id' => e($id . '-' . $value),
'name' => $id,
'value' => e($value),
$wireModelAttribute => $statePath,
],
escape: false,
);
@endphp
<label class="fi-fo-radio-label">
<input type="radio"
{{ $inputAttributes->class([
'fi-radio-input',
'fi-valid' => !$errors->has($statePath),
'fi-invalid' => $errors->has($statePath),
]) }} />
<div class="fi-fo-radio-label-text">
<p>
{{ $label }}
</p>
@if ($hasDescription($value))
<p class="fi-fo-radio-label-description">
{{ $getDescription($value) }}
</p>
@endif
</div>
</label>
@endforeach
</div>
</x-dynamic-component>
@@ -0,0 +1,224 @@
@php
use Filament\Actions\Action;
use Filament\Support\Enums\Alignment;
use Illuminate\View\ComponentAttributeBag;
$fieldWrapperView = $getFieldWrapperView();
$items = $getItems();
$addAction = $getAction($getAddActionName());
$addActionAlignment = $getAddActionAlignment();
$addBetweenAction = $getAction($getAddBetweenActionName());
$cloneAction = $getAction($getCloneActionName());
$collapseAllAction = $getAction($getCollapseAllActionName());
$expandAllAction = $getAction($getExpandAllActionName());
$deleteAction = $getAction($getDeleteActionName());
$moveDownAction = $getAction($getMoveDownActionName());
$moveUpAction = $getAction($getMoveUpActionName());
$reorderAction = $getAction($getReorderActionName());
$extraItemActions = $getExtraItemActions();
$hasItemNumbers = $hasItemNumbers();
$hasItemHeaders = $hasItemHeaders();
$isAddable = $isAddable();
$isCloneable = $isCloneable();
$isCollapsible = $isCollapsible();
$isDeletable = $isDeletable();
$isReorderableWithButtons = $isReorderableWithButtons();
$isReorderableWithDragAndDrop = $isReorderableWithDragAndDrop();
$collapseAllActionIsVisible = $isCollapsible && $collapseAllAction->isVisible();
$expandAllActionIsVisible = $isCollapsible && $expandAllAction->isVisible();
$persistCollapsed = $shouldPersistCollapsed();
$key = $getKey();
$statePath = $getStatePath();
$itemLabelHeadingTag = $getHeadingTag();
$isItemLabelTruncated = $isItemLabelTruncated();
$labelBetweenItems = $getLabelBetweenItems();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<div
{{ $attributes->merge($getExtraAttributes(), escape: false)->class(['fi-fo-repeater', 'fi-collapsible' => $isCollapsible]) }}>
@if ($collapseAllActionIsVisible || $expandAllActionIsVisible)
<div @class(['fi-fo-repeater-actions', 'fi-hidden' => count($items) < 2])>
@if ($collapseAllActionIsVisible)
<span x-on:click="$dispatch('repeater-collapse', '{{ $statePath }}')">
{{ $collapseAllAction }}
</span>
@endif
@if ($expandAllActionIsVisible)
<span x-on:click="$dispatch('repeater-expand', '{{ $statePath }}')">
{{ $expandAllAction }}
</span>
@endif
</div>
@endif
@if (count($items))
<ul x-sortable
{{ new ComponentAttributeBag()->grid($getGridColumns())->merge(
[
'data-sortable-animation-duration' => $getReorderAnimationDuration(),
'x-on:end.stop' =>
'$wire.mountAction(\'reorder\', { items: $event.target.sortable.toArray() }, { schemaComponent: \'' .
$key .
'\' })',
],
escape: false,
)->class(['fi-fo-repeater-items']) }}>
@foreach ($items as $itemKey => $item)
@php
$itemLabel = $getItemLabel($itemKey, $loop->index);
$visibleExtraItemActions = array_filter(
$extraItemActions,
fn(Action $action): bool => $action(['item' => $itemKey])->isVisible(),
);
$cloneAction = $cloneAction(['item' => $itemKey]);
$cloneActionIsVisible = $isCloneable && $cloneAction->isVisible();
$deleteAction = $deleteAction(['item' => $itemKey]);
$deleteActionIsVisible = $isDeletable && $deleteAction->isVisible();
$moveDownAction = $moveDownAction(['item' => $itemKey])->disabled($loop->last);
$moveDownActionIsVisible = $isReorderableWithButtons && $moveDownAction->isVisible();
$moveUpAction = $moveUpAction(['item' => $itemKey])->disabled($loop->first);
$moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible();
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
$hasItemHeader =
$hasItemHeaders &&
($reorderActionIsVisible ||
$moveUpActionIsVisible ||
$moveDownActionIsVisible ||
filled($itemLabel) ||
$cloneActionIsVisible ||
$deleteActionIsVisible ||
$isCollapsible ||
$visibleExtraItemActions);
@endphp
<li wire:ignore.self wire:key="{{ $item->getLivewireKey() }}.item" x-data="{
isCollapsed: @if ($persistCollapsed) $persist(@js($isCollapsed($item))).as(`repeater-${@js($key)}-${@js($itemKey)}-isCollapsed`) @else @js($isCollapsed($item)) @endif,
}"
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="{{ $itemKey }}"
@class([
'fi-fo-repeater-item',
'fi-fo-repeater-item-has-header' => $hasItemHeader,
]) x-bind:class="{ 'fi-collapsed': isCollapsed }">
@if ($hasItemHeader)
<div @if ($isCollapsible) x-on:click.stop="isCollapsed = !isCollapsed" @endif
class="fi-fo-repeater-item-header">
@if ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible)
<ul class="fi-fo-repeater-item-header-start-actions">
@if ($reorderActionIsVisible)
<li x-on:click.stop>
{{ $reorderAction->extraAttributes(['x-sortable-handle' => true], merge: true) }}
</li>
@endif
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
<li x-on:click.stop>
{{ $moveUpAction }}
</li>
<li x-on:click.stop>
{{ $moveDownAction }}
</li>
@endif
</ul>
@endif
@if (filled($itemLabel))
<{{ $itemLabelHeadingTag }} @class([
'fi-fo-repeater-item-header-label',
'fi-truncated' => $isItemLabelTruncated,
])>
{{ $itemLabel }}
@if ($hasItemNumbers)
{{ $loop->iteration }}
@endif
</{{ $itemLabelHeadingTag }}>
@endif
@if ($cloneActionIsVisible || $deleteActionIsVisible || $isCollapsible || $visibleExtraItemActions)
<ul class="fi-fo-repeater-item-header-end-actions">
@foreach ($visibleExtraItemActions as $extraItemAction)
<li x-on:click.stop>
{{ $extraItemAction(['item' => $itemKey]) }}
</li>
@endforeach
@if ($cloneActionIsVisible)
<li x-on:click.stop>
{{ $cloneAction }}
</li>
@endif
@if ($deleteActionIsVisible)
<li x-on:click.stop>
{{ $deleteAction }}
</li>
@endif
@if ($isCollapsible)
<li class="fi-fo-repeater-item-header-collapsible-actions"
x-on:click.stop="isCollapsed = !isCollapsed">
<div class="fi-fo-repeater-item-header-collapse-action">
{{ $getAction('collapse') }}
</div>
<div class="fi-fo-repeater-item-header-expand-action">
{{ $getAction('expand') }}
</div>
</li>
@endif
</ul>
@endif
</div>
@endif
<div x-show="! isCollapsed" class="fi-fo-repeater-item-content">
{{ $item }}
</div>
</li>
@if (!$loop->last)
@if ($isAddable && $addBetweenAction(['afterItem' => $itemKey])->isVisible())
<li class="fi-fo-repeater-add-between-items-ctn">
<div class="fi-fo-repeater-add-between-items">
{{ $addBetweenAction(['afterItem' => $itemKey]) }}
</div>
</li>
@elseif (filled($labelBetweenItems))
<li class="fi-fo-repeater-label-between-items-ctn">
<div class="fi-fo-repeater-label-between-items-divider-before"></div>
<span class="fi-fo-repeater-label-between-items">
{{ $labelBetweenItems }}
</span>
<div class="fi-fo-repeater-label-between-items-divider-after"></div>
</li>
@endif
@endif
@endforeach
</ul>
@endif
@if ($isAddable && $addAction->isVisible())
<div @class([
'fi-fo-repeater-add',
$addActionAlignment instanceof Alignment
? 'fi-align-' . $addActionAlignment->value
: $addActionAlignment,
])>
{{ $addAction }}
</div>
@endif
</div>
</x-dynamic-component>
@@ -0,0 +1,125 @@
@php
use Filament\Actions\Action;
use Filament\Support\Enums\Alignment;
use Illuminate\View\ComponentAttributeBag;
$fieldWrapperView = $getFieldWrapperView();
$items = $getItems();
$addAction = $getAction($getAddActionName());
$addActionAlignment = $getAddActionAlignment();
$cloneAction = $getAction($getCloneActionName());
$deleteAction = $getAction($getDeleteActionName());
$moveDownAction = $getAction($getMoveDownActionName());
$moveUpAction = $getAction($getMoveUpActionName());
$reorderAction = $getAction($getReorderActionName());
$extraItemActions = $getExtraItemActions();
$isAddable = $isAddable();
$isCloneable = $isCloneable();
$isDeletable = $isDeletable();
$isReorderableWithButtons = $isReorderableWithButtons();
$isReorderableWithDragAndDrop = $isReorderableWithDragAndDrop();
$key = $getKey();
$statePath = $getStatePath();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<div {{ $attributes->merge($getExtraAttributes(), escape: false)->class(['fi-fo-simple-repeater']) }}>
@if (count($items))
<ul x-sortable
{{ new ComponentAttributeBag()->grid($getGridColumns())->merge(
[
'data-sortable-animation-duration' => $getReorderAnimationDuration(),
'x-on:end.stop' =>
'$wire.mountAction(\'reorder\', { items: $event.target.sortable.toArray() }, { schemaComponent: \'' .
$key .
'\' })',
],
escape: false,
)->class(['fi-fo-simple-repeater-items']) }}>
@foreach ($items as $itemKey => $item)
@php
$visibleExtraItemActions = array_filter(
$extraItemActions,
fn(Action $action): bool => $action(['item' => $itemKey])->isVisible(),
);
$cloneAction = $cloneAction(['item' => $itemKey]);
$cloneActionIsVisible = $isCloneable && $cloneAction->isVisible();
$deleteAction = $deleteAction(['item' => $itemKey]);
$deleteActionIsVisible = $isDeletable && $deleteAction->isVisible();
$moveDownAction = $moveDownAction(['item' => $itemKey])->disabled($loop->last);
$moveDownActionIsVisible = $isReorderableWithButtons && $moveDownAction->isVisible();
$moveUpAction = $moveUpAction(['item' => $itemKey])->disabled($loop->first);
$moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible();
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
@endphp
<li wire:key="{{ $item->getLivewireKey() }}.item" x-sortable-item="{{ $itemKey }}"
class="fi-fo-simple-repeater-item">
<div class="fi-fo-simple-repeater-item-content">
{{ $item }}
</div>
@if (
$reorderActionIsVisible ||
$moveUpActionIsVisible ||
$moveDownActionIsVisible ||
$cloneActionIsVisible ||
$deleteActionIsVisible ||
$visibleExtraItemActions)
<ul class="fi-fo-simple-repeater-item-actions">
@if ($reorderActionIsVisible)
<li x-on:click.stop>
{{ $reorderAction->extraAttributes(['x-sortable-handle' => true], merge: true) }}
</li>
@endif
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
<li x-on:click.stop>
{{ $moveUpAction }}
</li>
<li x-on:click.stop>
{{ $moveDownAction }}
</li>
@endif
@foreach ($visibleExtraItemActions as $extraItemAction)
<li x-on:click.stop>
{{ $extraItemAction(['item' => $itemKey]) }}
</li>
@endforeach
@if ($cloneActionIsVisible)
<li x-on:click.stop>
{{ $cloneAction }}
</li>
@endif
@if ($deleteActionIsVisible)
<li x-on:click.stop>
{{ $deleteAction }}
</li>
@endif
</ul>
@endif
</li>
@endforeach
</ul>
@endif
@if ($isAddable && $addAction->isVisible())
<div @class([
'fi-fo-simple-repeater-add',
$addActionAlignment instanceof Alignment
? 'fi-align-' . $addActionAlignment->value
: $addActionAlignment,
])>
{{ $addAction }}
</div>
@endif
</div>
</x-dynamic-component>
@@ -0,0 +1,217 @@
@php
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\VerticalAlignment;
use Illuminate\Support\Js;
use Illuminate\View\ComponentAttributeBag;
$fieldWrapperView = $getFieldWrapperView();
$items = $getItems();
$addAction = $getAction($getAddActionName());
$addActionAlignment = $getAddActionAlignment();
$addBetweenAction = $getAction($getAddBetweenActionName());
$cloneAction = $getAction($getCloneActionName());
$deleteAction = $getAction($getDeleteActionName());
$moveDownAction = $getAction($getMoveDownActionName());
$moveUpAction = $getAction($getMoveUpActionName());
$reorderAction = $getAction($getReorderActionName());
$extraItemActions = $getExtraItemActions();
$isAddable = $isAddable();
$isCloneable = $isCloneable();
$isDeletable = $isDeletable();
$isReorderableWithButtons = $isReorderableWithButtons();
$isReorderableWithDragAndDrop = $isReorderableWithDragAndDrop();
$key = $getKey();
$statePath = $getStatePath();
$tableColumns = $getTableColumns();
$isCompact = $isCompact();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<div
{{ $attributes->merge($getExtraAttributes(), escape: false)->class(['fi-fo-table-repeater', 'fi-compact' => $isCompact]) }}>
@if (count($items))
<table>
<thead>
<tr>
@if (count($items) > 1 && ($isReorderableWithButtons || $isReorderableWithDragAndDrop))
<th class="fi-fo-table-repeater-empty-header-cell"></th>
@endif
@foreach ($tableColumns as $column)
<th @class([
'fi-wrapped' => $column->canHeaderWrap(),
($columnAlignment = $column->getAlignment()) instanceof Alignment
? 'fi-align-' . $columnAlignment->value
: $columnAlignment,
]) @style([
'width: ' . e($columnWidth = $column->getWidth()) => filled($columnWidth),
])>
@if (!$column->isHeaderLabelHidden())
{{ $column->getLabel() }}@if ($column->isMarkedAsRequired())
<sup class="fi-fo-table-repeater-header-required-mark">*</sup>
@endif
@else
<span class="fi-sr-only">
{{ $column->getLabel() }}
</span>
@endif
</th>
@endforeach
@if (count($extraItemActions) || $isCloneable || $isDeletable)
<th class="fi-fo-table-repeater-empty-header-cell"></th>
@endif
</tr>
</thead>
<tbody x-sortable
{{ new ComponentAttributeBag()->merge(
[
'data-sortable-animation-duration' => $getReorderAnimationDuration(),
'x-on:end.stop' =>
'$wire.mountAction(\'reorder\', { items: $event.target.sortable.toArray() }, { schemaComponent: \'' .
$key .
'\' })',
],
escape: false,
) }}>
@foreach ($items as $itemKey => $item)
@php
$visibleExtraItemActions = array_filter(
$extraItemActions,
fn(Action $action): bool => $action(['item' => $itemKey])->isVisible(),
);
$cloneAction = $cloneAction(['item' => $itemKey]);
$cloneActionIsVisible = $isCloneable && $cloneAction->isVisible();
$deleteAction = $deleteAction(['item' => $itemKey]);
$deleteActionIsVisible = $isDeletable && $deleteAction->isVisible();
$moveDownAction = $moveDownAction(['item' => $itemKey])->disabled($loop->last);
$moveDownActionIsVisible = $isReorderableWithButtons && $moveDownAction->isVisible();
$moveUpAction = $moveUpAction(['item' => $itemKey])->disabled($loop->first);
$moveUpActionIsVisible = $isReorderableWithButtons && $moveUpAction->isVisible();
$reorderActionIsVisible = $isReorderableWithDragAndDrop && $reorderAction->isVisible();
$itemStatePath = $item->getStatePath();
@endphp
<tr wire:key="{{ $item->getLivewireKey() }}.item" x-sortable-item="{{ $itemKey }}">
@if (count($items) > 1 && ($isReorderableWithButtons || $isReorderableWithDragAndDrop))
<td>
@if ($reorderActionIsVisible || $moveUpActionIsVisible || $moveDownActionIsVisible)
<div class="fi-fo-table-repeater-actions">
@if ($reorderActionIsVisible)
<div x-on:click.stop>
{{ $reorderAction->extraAttributes(['x-sortable-handle' => true], merge: true) }}
</div>
@endif
@if ($moveUpActionIsVisible || $moveDownActionIsVisible)
<div x-on:click.stop>
{{ $moveUpAction }}
</div>
<div x-on:click.stop>
{{ $moveDownAction }}
</div>
@endif
</div>
@endif
</td>
@endif
@php
$counter = 0;
@endphp
@foreach ($item->getComponents(withHidden: true) as $schemaComponent)
@php
throw_unless(
$schemaComponent instanceof \Filament\Schemas\Components\Component,
new \Exception(
'Table repeaters must only contain schema components, but [' .
$schemaComponent::class .
'] was used.',
),
);
@endphp
@if (count($tableColumns) > $counter)
@if ($schemaComponent instanceof \Filament\Forms\Components\Hidden)
{{ $schemaComponent }}
@else
@php
$counter++;
@endphp
@if ($schemaComponent->isVisible())
@php
$currentColumn = $tableColumns[$counter - 1] ?? null;
$columnVerticalAlignment = $currentColumn?->getVerticalAlignment();
@endphp
<td @class([
$columnVerticalAlignment instanceof VerticalAlignment
? 'fi-vertical-align-' . $columnVerticalAlignment->value
: (is_string($columnVerticalAlignment)
? $columnVerticalAlignment
: ''),
])>
{!! $schemaComponent->toSchemaHtml() !!}
</td>
@else
<td class="fi-hidden"></td>
@endif
@endif
@endif
@endforeach
@if (count($extraItemActions) || $isCloneable || $isDeletable)
<td>
@if ($visibleExtraItemActions || $cloneActionIsVisible || $deleteActionIsVisible)
<div class="fi-fo-table-repeater-actions">
@foreach ($visibleExtraItemActions as $extraItemAction)
<div x-on:click.stop>
{{ $extraItemAction(['item' => $itemKey]) }}
</div>
@endforeach
@if ($cloneActionIsVisible)
<div x-on:click.stop>
{{ $cloneAction }}
</div>
@endif
@if ($deleteActionIsVisible)
<div x-on:click.stop>
{{ $deleteAction }}
</div>
@endif
</div>
@endif
</td>
@endif
</tr>
@endforeach
</tbody>
</table>
@endif
@if ($isAddable && $addAction->isVisible())
<div @class([
'fi-fo-table-repeater-add',
$addActionAlignment instanceof Alignment
? 'fi-align-' . $addActionAlignment->value
: $addActionAlignment,
])>
{{ $addAction }}
</div>
@endif
</div>
</x-dynamic-component>
@@ -0,0 +1,204 @@
@php
$customBlocks = $getCustomBlocks();
$groupedCustomBlocks = $getGroupedCustomBlocks();
$extraAttributeBag = $getExtraAttributeBag();
$fieldWrapperView = $getFieldWrapperView();
$id = $getId();
$isDisabled = $isDisabled();
$label = $getLabel();
$livewireKey = $getLivewireKey();
$key = $getKey();
$mergeTags = $getMergeTags();
$statePath = $getStatePath();
$mentions = $getMentionsForJs();
$toolbarButtons = $getToolbarButtons();
$tools = $getTools();
$floatingToolbars = $getFloatingToolbars();
$linkProtocols = $getLinkProtocols();
$fileAttachmentsMaxSize = $getFileAttachmentsMaxSize();
$fileAttachmentsAcceptedFileTypes = $getFileAttachmentsAcceptedFileTypes();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<x-filament::input.wrapper :disabled="$isDisabled" :valid="!$errors->has($statePath)" x-cloak :attributes="\Filament\Support\prepare_inherited_attributes($extraAttributeBag)->class(['fi-fo-rich-editor'])">
<div x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('rich-editor', 'filament/forms') }}"
x-data="richEditorFormComponent({
acceptedFileTypes: @js($fileAttachmentsAcceptedFileTypes),
acceptedFileTypesValidationMessage: @js($fileAttachmentsAcceptedFileTypes ? __('filament-forms::components.rich_editor.file_attachments_accepted_file_types_message', ['values' => implode(', ', $fileAttachmentsAcceptedFileTypes)]) : null),
activePanel: @js($getActivePanel()),
canAttachFiles: @js($hasFileAttachments()),
deleteCustomBlockButtonIconHtml: @js(\Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::Trash, alias: \Filament\Forms\View\FormsIconAlias::COMPONENTS_RICH_EDITOR_PANELS_CUSTOM_BLOCK_DELETE_BUTTON)->toHtml()),
editCustomBlockButtonIconHtml: @js(\Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::PencilSquare, alias: \Filament\Forms\View\FormsIconAlias::COMPONENTS_RICH_EDITOR_PANELS_CUSTOM_BLOCK_EDIT_BUTTON)->toHtml()),
extensions: @js($getTipTapJsExtensions()),
floatingToolbars: @js($floatingToolbars),
getMentionLabelsUsing: async (mentions) => {
return await $wire.callSchemaComponentMethod(
@js($key),
'getMentionLabelsForJs', { mentions },
)
},
getMentionSearchResultsUsing: async (query, char) => {
return await $wire.callSchemaComponentMethod(
@js($key),
'getMentionSearchResultsForJs', { search: query, char },
)
},
hasResizableImages: @js($hasResizableImages()),
isDisabled: @js($isDisabled),
label: @js($label),
isLiveDebounced: @js($isLiveDebounced()),
isLiveOnBlur: @js($isLiveOnBlur()),
key: @js($key),
linkProtocols: @js($linkProtocols),
liveDebounce: @js($getNormalizedLiveDebounce()),
livewireId: @js($this->getId()),
maxFileSize: @js($fileAttachmentsMaxSize),
maxFileSizeValidationMessage: @js($fileAttachmentsMaxSize ? trans_choice('filament-forms::components.rich_editor.file_attachments_max_size_message', $fileAttachmentsMaxSize, ['max' => $fileAttachmentsMaxSize]) : null),
mentions: @js($mentions),
mergeTags: @js($mergeTags),
noMergeTagSearchResultsMessage: @js($getNoMergeTagSearchResultsMessage()),
placeholder: @js($getPlaceholder()),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')", isOptimisticallyLive: false) }},
statePath: @js($statePath),
textColors: @js($getTextColorsForJs()),
uploadingFileMessage: @js($getUploadingFileMessage()),
})"
x-bind:class="{
'fi-fo-rich-editor-uploading-file': isUploadingFile,
}"
wire:ignore wire:key="{{ $livewireKey }}.{{ substr(md5(serialize([$isDisabled])), 0, 64) }}">
@if (!$isDisabled && filled($toolbarButtons))
<div class="fi-fo-rich-editor-toolbar">
@foreach ($toolbarButtons as $button => $buttonGroup)
<div class="fi-fo-rich-editor-toolbar-group">
@foreach ($buttonGroup as $button)
@if (is_string($button))
{{ $tools[$button] ?? throw new LogicException("Toolbar button [{$button}] cannot be found.") }}
@else
{{ $button }}
@endif
@endforeach
</div>
@endforeach
</div>
@endif
<div x-show="isUploadingFile" x-cloak class="fi-fo-rich-editor-uploading-file-message">
{{ \Filament\Support\generate_loading_indicator_html() }}
<span>
{{ $getUploadingFileMessage() }}
</span>
</div>
<div x-show="! isUploadingFile && fileValidationMessage" x-cloak
class="fi-fo-rich-editor-file-validation-message">
<span x-text="fileValidationMessage" x-show="! isUploadingFile && fileValidationMessage"></span>
</div>
<div {{ $getExtraInputAttributeBag()->class(['fi-fo-rich-editor-main']) }}>
<div class="fi-fo-rich-editor-content fi-prose" x-ref="editor">
@foreach ($floatingToolbars as $nodeName => $buttons)
<div x-ref="floatingToolbar::{{ $nodeName }}"
class="fi-fo-rich-editor-floating-toolbar fi-not-prose">
@foreach ($buttons as $button)
@if (is_string($button))
{{ $tools[$button] }}
@else
{{ $button }}
@endif
@endforeach
</div>
@endforeach
</div>
@if (!$isDisabled)
<div x-show="isPanelActive()" x-cloak class="fi-fo-rich-editor-panels">
<div x-show="isPanelActive('customBlocks')" x-cloak class="fi-fo-rich-editor-panel">
<div class="fi-fo-rich-editor-panel-header">
<p class="fi-fo-rich-editor-panel-heading">
{{ __('filament-forms::components.rich_editor.tools.custom_blocks') }}
</p>
<div class="fi-fo-rich-editor-panel-close-btn-ctn">
<button type="button" x-on:click="togglePanel()" class="fi-icon-btn">
{{ \Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::XMark, alias: \Filament\Forms\View\FormsIconAlias::COMPONENTS_RICH_EDITOR_PANELS_CUSTOM_BLOCKS_CLOSE_BUTTON) }}
</button>
</div>
</div>
<div class="fi-fo-rich-editor-custom-blocks-ctn">
@foreach ($groupedCustomBlocks as $customBlockGroupLabel => $groupBlocks)
@if (filled($customBlockGroupLabel))
<h4 class="fi-fo-rich-editor-custom-blocks-group-header">
{{ $customBlockGroupLabel }}
</h4>
@endif
<div class="fi-fo-rich-editor-custom-blocks-list">
@foreach ($groupBlocks as $block)
@php
$blockId = $block::getId();
@endphp
<button draggable="true" type="button" x-data="{ isLoading: false }"
x-on:click="
isLoading = true
$wire.mountAction(
'customBlock',
{ editorSelection, id: @js($blockId), mode: 'insert' },
{ schemaComponent: @js($key) },
)
"
x-on:dragstart="$event.dataTransfer.setData('customBlock', @js($blockId))"
x-on:open-modal.window="isLoading = false"
x-on:run-rich-editor-commands.window="isLoading = false"
class="fi-fo-rich-editor-custom-block-btn">
{{ \Filament\Support\generate_loading_indicator_html(
new \Illuminate\View\ComponentAttributeBag([
'x-show' => 'isLoading',
]),
) }}
{{ $block::getLabel() }}
</button>
@endforeach
</div>
@endforeach
</div>
</div>
<div x-show="isPanelActive('mergeTags')" x-cloak class="fi-fo-rich-editor-panel">
<div class="fi-fo-rich-editor-panel-header">
<p class="fi-fo-rich-editor-panel-heading">
{{ __('filament-forms::components.rich_editor.tools.merge_tags') }}
</p>
<div class="fi-fo-rich-editor-panel-close-btn-ctn">
<button type="button" x-on:click="togglePanel()" class="fi-icon-btn">
{{ \Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::XMark, alias: \Filament\Forms\View\FormsIconAlias::COMPONENTS_RICH_EDITOR_PANELS_MERGE_TAGS_CLOSE_BUTTON) }}
</button>
</div>
</div>
<div class="fi-fo-rich-editor-merge-tags-list">
@foreach ($mergeTags as $tagId => $tagLabel)
<button draggable="true" type="button"
x-on:click="insertMergeTag(@js($tagId))"
x-on:dragstart="$event.dataTransfer.setData('mergeTag', @js($tagId))"
class="fi-fo-rich-editor-merge-tag-btn">
<span data-type="mergeTag" data-id="{{ $tagId }}">
{{ $tagLabel }}
</span>
</button>
@endforeach
</div>
</div>
</div>
@endif
</div>
</div>
</x-filament::input.wrapper>
</x-dynamic-component>
@@ -0,0 +1,180 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$extraInputAttributeBag = $getExtraInputAttributeBag();
$canSelectPlaceholder = $canSelectPlaceholder();
$isAutofocused = $isAutofocused();
$isDisabled = $isDisabled();
$isMultiple = $isMultiple();
$isReorderable = $isReorderable();
$isSearchable = $isSearchable();
$hasInitialNoOptionsMessage = $hasInitialNoOptionsMessage();
$hasDynamicOptions = $hasDynamicOptions();
$canOptionLabelsWrap = $canOptionLabelsWrap();
$isRequired = $isRequired();
$isConcealed = $isConcealed();
$isHtmlAllowed = $isHtmlAllowed();
$isNative = !($isSearchable || $isMultiple || $isHtmlAllowed) && $isNative();
$isPrefixInline = $isPrefixInline();
$isSuffixInline = $isSuffixInline();
$key = $getKey();
$id = $getId();
$prefixActions = $getPrefixActions();
$prefixIcon = $getPrefixIcon();
$prefixIconColor = $getPrefixIconColor();
$prefixLabel = $getPrefixLabel();
$suffixActions = $getSuffixActions();
$suffixIcon = $getSuffixIcon();
$suffixIconColor = $getSuffixIconColor();
$suffixLabel = $getSuffixLabel();
$statePath = $getStatePath();
$state = $getRawState();
$livewireKey = $getLivewireKey();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" class="fi-fo-select-wrp">
<x-filament::input.wrapper :disabled="$isDisabled" :inline-prefix="$isPrefixInline" :inline-suffix="$isSuffixInline" :prefix="$prefixLabel" :prefix-actions="$prefixActions"
:prefix-icon="$prefixIcon" :prefix-icon-color="$prefixIconColor" :suffix="$suffixLabel" :suffix-actions="$suffixActions" :suffix-icon="$suffixIcon" :suffix-icon-color="$suffixIconColor"
:valid="!$errors->has($statePath)"
:x-on:focus-input.stop="$isNative ? '$el.querySelector(\'select\')?.focus()' : '$el.querySelector(\'.fi-select-input-btn\')?.focus()'"
:attributes="\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())->class([
'fi-fo-select',
'fi-fo-select-has-inline-prefix' =>
$isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
'fi-fo-select-native' => $isNative,
])">
@if ($isNative)
@php
$options = $getOptions();
@endphp
<select
{{ $extraInputAttributeBag->merge(
[
'autofocus' => $isAutofocused,
'disabled' => $isDisabled,
'id' => $id,
'required' => $isRequired && !$isConcealed,
'wire:key' => $hasDynamicOptions ? $livewireKey . '.' . substr(md5(serialize($options)), 0, 64) : null,
$applyStateBindingModifiers('wire:model') => $statePath,
],
escape: false,
)->class([
'fi-select-input',
'fi-select-input-has-inline-prefix' =>
$isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
]) }}>
@if ($canSelectPlaceholder)
<option value="">
@if (!$isDisabled)
{{ $getPlaceholder() }}
@endif
</option>
@endif
@foreach ($options as $value => $label)
@if (is_array($label))
<optgroup label="{{ $value }}">
@foreach ($label as $groupedValue => $groupedLabel)
<option @disabled($isOptionDisabled($groupedValue, $groupedLabel)) value="{{ $groupedValue }}">
@if ($isHtmlAllowed)
{!! $groupedLabel !!}
@else
{{ $groupedLabel }}
@endif
</option>
@endforeach
</optgroup>
@else
<option @disabled($isOptionDisabled($value, $label)) value="{{ $value }}">
@if ($isHtmlAllowed)
{!! $label !!}
@else
{{ $label }}
@endif
</option>
@endif
@endforeach
</select>
@else
<div class="fi-hidden" x-data="{
isDisabled: @js($isDisabled),
init() {
const container = $el.nextElementSibling
container.dispatchEvent(
new CustomEvent('set-select-property', {
detail: { isDisabled: this.isDisabled },
}),
)
},
}"></div>
<div x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('select', 'filament/forms') }}"
x-data="selectFormComponent({
canOptionLabelsWrap: @js($canOptionLabelsWrap),
canSelectPlaceholder: @js($canSelectPlaceholder),
getOptionLabelUsing: async () => {
return await Livewire.fireAction(
$wire.__instance,
'callSchemaComponentMethod',
[@js($key), 'getOptionLabel'], { async: true },
)
},
getOptionLabelsUsing: async () => {
return await Livewire.fireAction(
$wire.__instance,
'callSchemaComponentMethod',
[@js($key), 'getOptionLabelsForJs'], { async: true },
)
},
getOptionsUsing: async () => {
return await Livewire.fireAction(
$wire.__instance,
'callSchemaComponentMethod',
[@js($key), 'getOptionsForJs'], { async: true },
)
},
getSearchResultsUsing: async (search) => {
return await Livewire.fireAction(
$wire.__instance,
'callSchemaComponentMethod',
[@js($key), 'getSearchResultsForJs', { search }], { async: true },
)
},
hasDynamicOptions: @js($hasDynamicOptions),
hasDynamicSearchResults: @js($hasDynamicSearchResults()),
hasInitialNoOptionsMessage: @js($hasInitialNoOptionsMessage),
initialOptionLabel: @js(blank($state) || $isMultiple ? null : $getOptionLabel()),
initialOptionLabels: @js(filled($state) && $isMultiple ? $getOptionLabelsForJs() : []),
initialState: @js($state),
isAutofocused: @js($isAutofocused),
isDisabled: @js($isDisabled),
isHtmlAllowed: @js($isHtmlAllowed),
isMultiple: @js($isMultiple),
isReorderable: @js($isReorderable),
isSearchable: @js($isSearchable),
livewireId: @js($this->getId()),
loadingMessage: @js($getLoadingMessage()),
maxItems: @js($getMaxItems()),
maxItemsMessage: @js($getMaxItemsMessage()),
noOptionsMessage: @js($getNoOptionsMessage()),
noSearchResultsMessage: @js($getNoSearchResultsMessage()),
options: @js($getOptionsForJs()),
optionsLimit: @js($getOptionsLimit()),
placeholder: @js($getPlaceholder()),
position: @js($getPosition()),
searchDebounce: @js($getSearchDebounce()),
searchingMessage: @js($getSearchingMessage()),
searchPrompt: @js($getSearchPrompt()),
searchableOptionFields: @js($getSearchableOptionFields()),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
statePath: @js($statePath),
})" wire:ignore
wire:key="{{ $livewireKey }}.{{ substr(md5(serialize([$isDisabled, $isReorderable])), 0, 64) }}"
x-on:keydown.esc="select.dropdown.isActive && $event.stopPropagation()"
x-on:set-select-property="$event.detail.isDisabled ? select.disable() : select.enable()"
{{ $attributes->merge($getExtraAlpineAttributes(), escape: false)->class(['fi-select-input']) }}>
<div x-ref="select"></div>
</div>
@endif
</x-filament::input.wrapper>
</x-dynamic-component>
@@ -0,0 +1,48 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$isVertical = $isVertical();
$pipsMode = $getPipsMode();
$livewireKey = $getLivewireKey();
$isDisabled = $isDisabled();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" :inline-label-vertical-alignment="\Filament\Support\Enums\VerticalAlignment::Center">
<div x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('slider', 'filament/forms') }}"
x-data="sliderFormComponent({
arePipsStepped: @js($arePipsStepped()),
behavior: @js($getBehaviorForJs()),
decimalPlaces: @js($getDecimalPlaces()),
fillTrack: @js($getFillTrack()),
isDisabled: @js($isDisabled),
isRtl: @js($isRtl()),
isVertical: @js($isVertical),
maxDifference: @js($getMaxDifference()),
minDifference: @js($getMinDifference()),
maxValue: @js($getMaxValue()),
minValue: @js($getMinValue()),
nonLinearPoints: @js($getNonLinearPoints()),
pipsDensity: @js($getPipsDensity()),
pipsFilter: @js($getPipsFilterForJs()),
pipsFormatter: @js($getPipsFormatterForJs()),
pipsMode: @js($pipsMode),
pipsValues: @js($getPipsValues()),
rangePadding: @js($getRangePadding()),
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$getStatePath()}')") }},
step: @js($getStep()),
tooltips: @js($getTooltipsForJs()),
})" wire:ignore
wire:key="{{ $livewireKey }}.{{ substr(md5(serialize([$isDisabled])), 0, 64) }}"
{{ $attributes->merge(
[
'id' => $getId(),
],
escape: false,
)->merge($getExtraAttributes(), escape: false)->merge($getExtraAlpineAttributes(), escape: false)->class([
'fi-fo-slider',
'fi-fo-slider-has-pips' => $pipsMode,
'fi-fo-slider-has-tooltips' => $hasTooltips(),
'fi-fo-slider-vertical' => $isVertical,
]) }}>
</div>
</x-dynamic-component>
@@ -0,0 +1,33 @@
@php
use Filament\Forms\Components\TableSelect\Livewire\TableSelectLivewireComponent;
$fieldWrapperView = $getFieldWrapperView();
$extraAttributes = $getExtraAttributes();
$id = $getId();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field">
<div
{{ $attributes->merge(
[
'id' => $id,
],
escape: false,
)->merge($extraAttributes, escape: false) }}>
@livewire(
TableSelectLivewireComponent::class,
[
'isDisabled' => $isDisabled(),
'maxSelectableRecords' => $getMaxItems(),
'model' => $getModel(),
'record' => $getRecord(),
'relationshipName' => $getRelationshipName(),
'shouldIgnoreRelatedRecords' => $shouldIgnoreRelatedRecords(),
'tableConfiguration' => base64_encode($getTableConfiguration()),
'tableArguments' => $getTableArguments(),
$applyStateBindingModifiers('wire:model') => $getStatePath(),
],
key($getLivewireKey())
)
</div>
</x-dynamic-component>
@@ -0,0 +1,94 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$extraAttributes = $getExtraAttributes();
$extraInputAttributeBag = $getExtraInputAttributeBag();
$color = $getColor() ?? 'primary';
$id = $getId();
$isAutofocused = $isAutofocused();
$isDisabled = $isDisabled();
$isPrefixInline = $isPrefixInline();
$isReorderable = !$isDisabled && $isReorderable();
$isSuffixInline = $isSuffixInline();
$placeholder = $getPlaceholder();
$prefixActions = $getPrefixActions();
$prefixIcon = $getPrefixIcon();
$prefixIconColor = $getPrefixIconColor();
$prefixLabel = $getPrefixLabel();
$statePath = $getStatePath();
$suffixActions = $getSuffixActions();
$suffixIcon = $getSuffixIcon();
$suffixIconColor = $getSuffixIconColor();
$suffixLabel = $getSuffixLabel();
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" class="fi-fo-tags-input-wrp">
<x-filament::input.wrapper :disabled="$isDisabled" :inline-prefix="$isPrefixInline" :inline-suffix="$isSuffixInline" :prefix="$prefixLabel" :prefix-actions="$prefixActions"
:prefix-icon="$prefixIcon" :prefix-icon-color="$prefixIconColor" :suffix="$suffixLabel" :suffix-actions="$suffixActions" :suffix-icon="$suffixIcon" :suffix-icon-color="$suffixIconColor"
:valid="!$errors->has($statePath)" x-on:focus-input.stop="$el.querySelector('input')?.focus()" :attributes="\Filament\Support\prepare_inherited_attributes($attributes)
->merge($extraAttributes, escape: false)
->class(['fi-fo-tags-input', 'fi-disabled' => $isDisabled])">
<div x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('tags-input', 'filament/forms') }}"
x-data="tagsInputFormComponent({
state: $wire.{{ $applyStateBindingModifiers("\$entangle('{$statePath}')") }},
splitKeys: @js($getSplitKeys()),
})" {{ $getExtraAlpineAttributeBag() }}>
<input
{{ $extraInputAttributeBag->merge(
[
'autocomplete' => 'off',
'autofocus' => $isAutofocused,
'disabled' => $isDisabled,
'id' => $id,
'list' => $id . '-suggestions',
'placeholder' => filled($placeholder) ? e($placeholder) : null,
'type' => 'text',
'x-bind' => 'input',
],
escape: false,
)->class([
'fi-input',
'fi-input-has-inline-prefix' =>
$isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
'fi-input-has-inline-suffix' =>
$isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel)),
]) }} />
<datalist id="{{ $id }}-suggestions">
@foreach ($getSuggestions() as $suggestion)
<template x-bind:key="@js($suggestion)"
x-if="! (state?.includes(@js($suggestion)) ?? true)">
<option value="{{ $suggestion }}" />
</template>
@endforeach
</datalist>
<div wire:ignore>
<template x-cloak x-if="state?.length">
<div @if ($isReorderable) x-on:end.stop="reorderTags($event)"
x-sortable
data-sortable-animation-duration="{{ $getReorderAnimationDuration() }}" @endif
class="fi-fo-tags-input-tags-ctn">
<template x-for="(tag, index) in state" x-bind:key="`${tag}-${index}`">
<x-filament::badge :color="$color"
:x-bind:x-sortable-item="$isReorderable ? 'index' : null" :x-sortable-handle="$isReorderable ? '' : null"
@class([
'fi-reorderable' => $isReorderable,
])>
{{ $getTagPrefix() }}
<span x-text="tag"></span>
{{ $getTagSuffix() }}
<x-slot name="deleteButton" x-on:click.stop="deleteTag(tag)"
:x-bind:aria-label="'\''.__('filament-forms::components.tags_input.actions.delete.label').
': \' + tag'"></x-slot>
</x-filament::badge>
</template>
</div>
</template>
</div>
</div>
</x-filament::input.wrapper>
</x-dynamic-component>
@@ -0,0 +1,96 @@
@php
use Filament\Forms\Components\TextInput\Actions\HidePasswordAction;
use Filament\Forms\Components\TextInput\Actions\ShowPasswordAction;
$fieldWrapperView = $getFieldWrapperView();
$datalistOptions = $getDatalistOptions();
$extraAlpineAttributes = $getExtraAlpineAttributes();
$extraAttributeBag = $getExtraAttributeBag();
$id = $getId();
$isConcealed = $isConcealed();
$isDisabled = $isDisabled();
$isPasswordRevealable = $isPasswordRevealable();
$isPrefixInline = $isPrefixInline();
$isSuffixInline = $isSuffixInline();
$mask = $getMask();
$prefixActions = $getPrefixActions();
$prefixIcon = $getPrefixIcon();
$prefixIconColor = $getPrefixIconColor();
$prefixLabel = $getPrefixLabel();
$suffixActions = $getSuffixActions();
$suffixIcon = $getSuffixIcon();
$suffixIconColor = $getSuffixIconColor();
$suffixLabel = $getSuffixLabel();
$statePath = $getStatePath();
$placeholder = $getPlaceholder();
if ($isPasswordRevealable) {
$xData = '{ isPasswordRevealed: false }';
} elseif (count($extraAlpineAttributes) || filled($mask)) {
$xData = '{}';
} else {
$xData = null;
}
if ($isPasswordRevealable) {
$type = null;
} elseif (filled($mask)) {
$type = 'text';
} else {
$type = $getType();
}
$inputAttributes = $getExtraInputAttributeBag()
->merge($extraAlpineAttributes, escape: false)
->merge(
[
'autocapitalize' => $getAutocapitalize(),
'autocomplete' => $getAutocomplete(),
'autofocus' => $isAutofocused(),
'disabled' => $isDisabled,
'id' => $id,
'inlinePrefix' => $isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
'inlineSuffix' => $isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel)),
'inputmode' => $getInputMode(),
'list' => $datalistOptions ? $id . '-list' : null,
'max' => !$isConcealed ? $getMaxValue() : null,
'maxlength' => !$isConcealed ? $getMaxLength() : null,
'min' => !$isConcealed ? $getMinValue() : null,
'minlength' => !$isConcealed ? $getMinLength() : null,
'placeholder' => filled($placeholder) ? e($placeholder) : null,
'readonly' => $isReadOnly(),
'required' => $isRequired() && !$isConcealed,
'step' => $getStep(),
'type' => $type,
$applyStateBindingModifiers('wire:model') => $statePath,
'x-bind:type' => $isPasswordRevealable ? 'isPasswordRevealed ? \'text\' : \'password\'' : null,
'x-mask' . ($mask instanceof \Filament\Support\RawJs ? ':dynamic' : '') => filled($mask) ? $mask : null,
],
escape: false,
)
->class([
'fi-revealable' => $isPasswordRevealable,
]);
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" :inline-label-vertical-alignment="\Filament\Support\Enums\VerticalAlignment::Center">
<x-filament::input.wrapper :disabled="$isDisabled" :inline-prefix="$isPrefixInline" :inline-suffix="$isSuffixInline" :prefix="$prefixLabel" :prefix-actions="$prefixActions"
:prefix-icon="$prefixIcon" :prefix-icon-color="$prefixIconColor" :suffix="$suffixLabel" :suffix-actions="$suffixActions" :suffix-icon="$suffixIcon" :suffix-icon-color="$suffixIconColor"
:valid="!$errors->has($statePath)" :x-data="$xData" x-on:focus-input.stop="$el.querySelector('input')?.focus()"
:attributes="\Filament\Support\prepare_inherited_attributes($extraAttributeBag)->class(['fi-fo-text-input'])">
<input
{{ $inputAttributes->class([
'fi-input',
'fi-input-has-inline-prefix' => $isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
'fi-input-has-inline-suffix' => $isSuffixInline && (count($suffixActions) || $suffixIcon || filled($suffixLabel)),
]) }} />
</x-filament::input.wrapper>
@if ($datalistOptions)
<datalist id="{{ $id }}-list">
@foreach ($datalistOptions as $option)
<option value="{{ $option }}"></option>
@endforeach
</datalist>
@endif
</x-dynamic-component>
@@ -0,0 +1,54 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$extraAttributeBag = $getExtraAttributeBag();
$isConcealed = $isConcealed();
$isDisabled = $isDisabled();
$rows = $getRows();
$placeholder = $getPlaceholder();
$shouldAutosize = $shouldAutosize();
$placeholder = $getPlaceholder();
$statePath = $getStatePath();
$initialHeight = ($rows ?? 2) * 1.5 + 0.75;
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" class="fi-fo-textarea-wrp">
<x-filament::input.wrapper :disabled="$isDisabled" :valid="!$errors->has($statePath)" :attributes="\Filament\Support\prepare_inherited_attributes($extraAttributeBag)->class([
'fi-fo-textarea',
'fi-autosizable' => $shouldAutosize,
])">
<div wire:ignore.self style="height: '{{ $initialHeight . 'rem' }}'">
<textarea x-load
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('textarea', 'filament/forms') }}"
x-data="textareaFormComponent({
initialHeight: @js($initialHeight),
shouldAutosize: @js($shouldAutosize),
state: $wire.$entangle('{{ $statePath }}'),
})"
@if ($shouldAutosize) x-intersect.once="resize()"
x-on:resize.window="resize()" @endif
x-model="state"
@if ($isGrammarlyDisabled()) data-gramm="false"
data-gramm_editor="false"
data-enable-grammarly="false" @endif
{{ $getExtraAlpineAttributeBag() }}
{{ $getExtraInputAttributeBag()->merge(
[
'autocomplete' => $getAutocomplete(),
'autofocus' => $isAutofocused(),
'cols' => $getCols(),
'disabled' => $isDisabled,
'id' => $getId(),
'maxlength' => !$isConcealed ? $getMaxLength() : null,
'minlength' => !$isConcealed ? $getMinLength() : null,
'placeholder' => filled($placeholder) ? e($placeholder) : null,
'readonly' => $isReadOnly(),
'required' => $isRequired() && !$isConcealed,
'rows' => $rows,
$applyStateBindingModifiers('wire:model') => $statePath,
],
escape: false,
) }}></textarea>
</div>
</x-filament::input.wrapper>
</x-dynamic-component>
@@ -0,0 +1,34 @@
@php
$fieldWrapperView = $getFieldWrapperView();
$id = $getId();
$isDisabled = $isDisabled();
$isMultiple = $isMultiple();
$statePath = $getStatePath();
$areButtonLabelsHidden = $areButtonLabelsHidden();
$wireModelAttribute = $applyStateBindingModifiers('wire:model');
$extraInputAttributeBag = $getExtraInputAttributeBag()->class(['fi-fo-toggle-buttons-input']);
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" tabindex="-1" class="fi-fo-toggle-buttons-wrp">
<div {{ $getExtraAttributeBag()->class(['fi-fo-toggle-buttons fi-btn-group']) }}>
@foreach ($getOptions() as $value => $label)
@php
$inputId = "{$id}-{$value}";
$shouldOptionBeDisabled = $isDisabled || $isOptionDisabled($value, $label);
$color = $getColor($value);
$icon = $getIcon($value);
$tooltip = $getTooltip($value);
@endphp
<input @disabled($shouldOptionBeDisabled) id="{{ $inputId }}"
@if (!$isMultiple) name="{{ $id }}" @endif
type="{{ $isMultiple ? 'checkbox' : 'radio' }}" value="{{ $value }}" wire:loading.attr="disabled"
{{ $wireModelAttribute }}="{{ $statePath }}" {{ $extraInputAttributeBag }} />
<x-filament::button :color="$color" :disabled="$shouldOptionBeDisabled" :for="$inputId" grouped :icon="$icon"
:label-sr-only="$areButtonLabelsHidden" tag="label" :tooltip="$tooltip">
{{ $label }}
</x-filament::button>
@endforeach
</div>
</x-dynamic-component>
@@ -0,0 +1,42 @@
@php
use Filament\Support\Enums\GridDirection;
use Illuminate\View\ComponentAttributeBag;
$fieldWrapperView = $getFieldWrapperView();
$gridDirection = $getGridDirection() ?? GridDirection::Column;
$id = $getId();
$isDisabled = $isDisabled();
$isInline = $isInline();
$isMultiple = $isMultiple();
$statePath = $getStatePath();
$areButtonLabelsHidden = $areButtonLabelsHidden();
$wireModelAttribute = $applyStateBindingModifiers('wire:model');
$extraInputAttributeBag = $getExtraInputAttributeBag()->class(['fi-fo-toggle-buttons-input']);
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" tabindex="-1" class="fi-fo-toggle-buttons-wrp">
<div
{{ $getExtraAttributeBag()->when(!$isInline, fn(ComponentAttributeBag $attributes) => $attributes->grid($getColumns(), $gridDirection))->class(['fi-fo-toggle-buttons', 'fi-inline' => $isInline]) }}>
@foreach ($getOptions() as $value => $label)
@php
$inputId = "{$id}-{$value}";
$shouldOptionBeDisabled = $isDisabled || $isOptionDisabled($value, $label);
$color = $getColor($value);
$icon = $getIcon($value);
$tooltip = $getTooltip($value);
@endphp
<div class="fi-fo-toggle-buttons-btn-ctn">
<input @if ($loop->first && $isAutofocused()) autofocus @endif @disabled($shouldOptionBeDisabled)
id="{{ $inputId }}" @if (!$isMultiple) name="{{ $id }}" @endif
type="{{ $isMultiple ? 'checkbox' : 'radio' }}" value="{{ $value }}"
{{ $wireModelAttribute }}="{{ $statePath }}" {{ $extraInputAttributeBag }} />
<x-filament::button :color="$color" :disabled="$shouldOptionBeDisabled" :for="$inputId" :icon="$icon"
:label-sr-only="$areButtonLabelsHidden" tag="label" :tooltip="$tooltip">
{{ $label }}
</x-filament::button>
</div>
@endforeach
</div>
</x-dynamic-component>
@@ -0,0 +1,37 @@
@php
use Illuminate\View\ComponentAttributeBag;
$fieldWrapperView = $getFieldWrapperView();
$statePath = $getStatePath();
$attributes = new ComponentAttributeBag()
->merge(
[
'aria-checked' => 'false',
'autofocus' => $isAutofocused(),
'disabled' => $isDisabled(),
'id' => $getId(),
'offColor' => $getOffColor() ?? 'gray',
'offIcon' => $getOffIcon(),
'onColor' => $getOnColor() ?? 'primary',
'onIcon' => $getOnIcon(),
'state' => '$wire.' . $applyStateBindingModifiers('$entangle(\'' . $statePath . '\')'),
'wire:loading.attr' => 'disabled',
'wire:target' => $statePath,
],
escape: false,
)
->merge($getExtraAttributes(), escape: false)
->merge($getExtraAlpineAttributes(), escape: false)
->class(['fi-fo-toggle']);
@endphp
<x-dynamic-component :component="$fieldWrapperView" :field="$field" :inline-label-vertical-alignment="\Filament\Support\Enums\VerticalAlignment::Center">
@if ($isInline())
<x-slot name="labelPrefix">
<x-filament::toggle :attributes="\Filament\Support\prepare_inherited_attributes($attributes)" />
</x-slot>
@else
<x-filament::toggle :attributes="\Filament\Support\prepare_inherited_attributes($attributes)" />
@endif
</x-dynamic-component>