mirror of
https://github.com/filamentphp/filament.git
synced 2026-08-31 01:14:50 +08:00
Basic select col implementation
This commit is contained in:
@@ -143,18 +143,6 @@ schemaComponents.forEach((component) => {
|
||||
})
|
||||
})
|
||||
|
||||
const supportComponents = ['select']
|
||||
|
||||
supportComponents.forEach((component) => {
|
||||
compile({
|
||||
...defaultOptions,
|
||||
entryPoints: [
|
||||
`./packages/support/resources/js/components/${component}.js`,
|
||||
],
|
||||
outfile: `./packages/support/dist/components/${component}.js`,
|
||||
})
|
||||
})
|
||||
|
||||
const tableColumns = ['checkbox', 'select', 'text-input', 'toggle']
|
||||
|
||||
tableColumns.forEach((column) => {
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -34,9 +34,10 @@ export default function selectFormComponent({
|
||||
statePath,
|
||||
}) {
|
||||
return {
|
||||
state,
|
||||
select: null,
|
||||
|
||||
state,
|
||||
|
||||
init() {
|
||||
this.select = new Select({
|
||||
element: this.$refs.select,
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
{{
|
||||
$attributes
|
||||
->merge($getExtraAlpineAttributes(), escape: false)
|
||||
->class(['fi-select'])
|
||||
->class(['fi-select-input'])
|
||||
}}
|
||||
>
|
||||
<div x-ref="select"></div>
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
||||
.fi-select-input {
|
||||
select.fi-select-input {
|
||||
@apply block w-full appearance-none border-none bg-transparent py-1.5 ps-3 pe-8 text-sm leading-6 text-gray-950 transition duration-75 focus:ring-0 focus:outline-none disabled:text-gray-500 disabled:[-webkit-text-fill-color:var(--color-gray-500)] dark:text-white dark:disabled:text-gray-400 dark:disabled:[-webkit-text-fill-color:var(--color-gray-400)] [&_optgroup]:bg-white [&_optgroup]:dark:bg-gray-900 [&_option]:bg-white [&_option]:dark:bg-gray-900;
|
||||
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
|
||||
@@ -15,8 +15,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.fi-select {
|
||||
& .fi-select-ctn {
|
||||
.fi-select-input {
|
||||
& .fi-select-input-ctn {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
@apply min-h-9;
|
||||
}
|
||||
|
||||
& .fi-select-btn {
|
||||
& .fi-select-input-btn {
|
||||
@apply flex min-h-9 w-full rounded-lg py-1.5 ps-3 pe-8 text-start text-sm leading-6 text-gray-950 focus:ring-0 focus:outline-none dark:text-white;
|
||||
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
|
||||
@@ -37,20 +37,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
& .fi-select-value-ctn {
|
||||
& .fi-select-input-value-ctn {
|
||||
@apply flex w-full items-center text-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
& .fi-select-value-badges-ctn {
|
||||
& .fi-select-input-value-badges-ctn {
|
||||
@apply flex flex-wrap gap-1.5;
|
||||
}
|
||||
|
||||
& .fi-select-value-label {
|
||||
& .fi-select-input-value-label {
|
||||
@apply flex-1;
|
||||
}
|
||||
|
||||
& .fi-select-value-remove-btn {
|
||||
& .fi-select-input-value-remove-btn {
|
||||
@apply text-gray-500 hover:text-gray-600 focus-visible:text-gray-600 focus-visible:outline-none dark:hover:text-gray-300 dark:focus-visible:text-gray-300;
|
||||
}
|
||||
|
||||
@@ -58,19 +58,19 @@
|
||||
@apply max-h-60 max-w-full!;
|
||||
}
|
||||
|
||||
& .fi-select-options-ctn {
|
||||
& .fi-select-input-options-ctn {
|
||||
@apply divide-y divide-gray-100 dark:divide-white/5;
|
||||
}
|
||||
|
||||
& .fi-select-option-group {
|
||||
& .fi-select-input-option-group {
|
||||
@apply divide-y divide-gray-100 dark:divide-white/5;
|
||||
}
|
||||
|
||||
& .fi-select-search-ctn {
|
||||
& .fi-select-input-search-ctn {
|
||||
@apply sticky top-0 z-10 bg-white dark:bg-gray-900;
|
||||
}
|
||||
|
||||
& .fi-select-option {
|
||||
& .fi-select-input-option {
|
||||
@apply min-w-px text-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
@@ -79,16 +79,16 @@
|
||||
@apply cursor-not-allowed opacity-70;
|
||||
}
|
||||
|
||||
& .fi-select-message {
|
||||
& .fi-select-input-message {
|
||||
@apply px-3 py-2 text-sm text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
& .fi-select-ctn.fi-select-ctn-option-labels-not-wrapped {
|
||||
& .fi-select-value-ctn > span {
|
||||
& .fi-select-input-ctn.fi-select-input-ctn-option-labels-not-wrapped {
|
||||
& .fi-select-input-value-ctn > span {
|
||||
@apply truncate text-nowrap break-normal;
|
||||
}
|
||||
|
||||
& .fi-select-option > span {
|
||||
& .fi-select-input-option > span {
|
||||
@apply truncate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
import { Select } from '../utilities/select.js'
|
||||
|
||||
export default function selectFormComponent({
|
||||
canOptionLabelsWrap,
|
||||
canSelectPlaceholder,
|
||||
isHtmlAllowed,
|
||||
getOptionLabelUsing,
|
||||
getOptionLabelsUsing,
|
||||
getOptionsUsing,
|
||||
getSearchResultsUsing,
|
||||
initialOptionLabel,
|
||||
initialOptionLabels,
|
||||
initialState,
|
||||
isAutofocused,
|
||||
isDisabled,
|
||||
isMultiple,
|
||||
isSearchable,
|
||||
hasDynamicOptions,
|
||||
hasDynamicSearchResults,
|
||||
livewireId,
|
||||
loadingMessage,
|
||||
maxItems,
|
||||
maxItemsMessage,
|
||||
noSearchResultsMessage,
|
||||
options,
|
||||
optionsLimit,
|
||||
placeholder,
|
||||
position,
|
||||
searchDebounce,
|
||||
searchingMessage,
|
||||
searchPrompt,
|
||||
searchableOptionFields,
|
||||
state,
|
||||
statePath,
|
||||
}) {
|
||||
return {
|
||||
state,
|
||||
select: null,
|
||||
|
||||
init() {
|
||||
this.select = new Select({
|
||||
element: this.$refs.select,
|
||||
options,
|
||||
placeholder,
|
||||
state: this.state,
|
||||
canOptionLabelsWrap,
|
||||
canSelectPlaceholder,
|
||||
initialOptionLabel,
|
||||
initialOptionLabels,
|
||||
initialState,
|
||||
isHtmlAllowed,
|
||||
isAutofocused,
|
||||
isDisabled,
|
||||
isMultiple,
|
||||
isSearchable,
|
||||
getOptionLabelUsing,
|
||||
getOptionLabelsUsing,
|
||||
getOptionsUsing,
|
||||
getSearchResultsUsing,
|
||||
hasDynamicOptions,
|
||||
hasDynamicSearchResults,
|
||||
searchPrompt,
|
||||
searchDebounce,
|
||||
loadingMessage,
|
||||
searchingMessage,
|
||||
noSearchResultsMessage,
|
||||
maxItems,
|
||||
maxItemsMessage,
|
||||
optionsLimit,
|
||||
position,
|
||||
searchableOptionFields,
|
||||
livewireId,
|
||||
statePath,
|
||||
onStateChange: (newState) => {
|
||||
this.state = newState
|
||||
},
|
||||
})
|
||||
|
||||
this.$watch('state', (newState) => {
|
||||
if (this.select && this.select.state !== newState) {
|
||||
this.select.state = newState
|
||||
this.select.updateSelectedDisplay()
|
||||
this.select.renderOptions()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
destroy() {
|
||||
if (this.select) {
|
||||
this.select.destroy()
|
||||
this.select = null
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -130,11 +130,11 @@ export class Select {
|
||||
|
||||
// Create the main container
|
||||
this.container = document.createElement('div')
|
||||
this.container.className = 'fi-select-ctn'
|
||||
this.container.className = 'fi-select-input-ctn'
|
||||
|
||||
if (!this.canOptionLabelsWrap) {
|
||||
this.container.classList.add(
|
||||
'fi-select-ctn-option-labels-not-wrapped',
|
||||
'fi-select-input-ctn-option-labels-not-wrapped',
|
||||
)
|
||||
}
|
||||
|
||||
@@ -142,13 +142,13 @@ export class Select {
|
||||
|
||||
// Create the button that toggles the dropdown
|
||||
this.selectButton = document.createElement('button')
|
||||
this.selectButton.className = 'fi-select-btn'
|
||||
this.selectButton.className = 'fi-select-input-btn'
|
||||
this.selectButton.type = 'button'
|
||||
this.selectButton.setAttribute('aria-expanded', 'false')
|
||||
|
||||
// Create the selected value display
|
||||
this.selectedDisplay = document.createElement('div')
|
||||
this.selectedDisplay.className = 'fi-select-value-ctn'
|
||||
this.selectedDisplay.className = 'fi-select-input-value-ctn'
|
||||
|
||||
// Update the selected display based on current state
|
||||
this.updateSelectedDisplay()
|
||||
@@ -163,7 +163,7 @@ export class Select {
|
||||
this.dropdown.style.display = 'none'
|
||||
|
||||
// Generate a unique ID for the dropdown
|
||||
this.dropdownId = `fi-select-dropdown-${Math.random().toString(36).substring(2, 11)}`
|
||||
this.dropdownId = `fi-select-input-dropdown-${Math.random().toString(36).substring(2, 11)}`
|
||||
this.dropdown.id = this.dropdownId
|
||||
|
||||
// Set aria-multiselectable for multi-select
|
||||
@@ -174,7 +174,7 @@ export class Select {
|
||||
// Add search input if searchable
|
||||
if (this.isSearchable) {
|
||||
this.searchContainer = document.createElement('div')
|
||||
this.searchContainer.className = 'fi-select-search-ctn'
|
||||
this.searchContainer.className = 'fi-select-input-search-ctn'
|
||||
|
||||
this.searchInput = document.createElement('input')
|
||||
this.searchInput.className = 'fi-input'
|
||||
@@ -308,7 +308,7 @@ export class Select {
|
||||
|
||||
// Set the appropriate class based on whether we have grouped options
|
||||
if (hasGroupedOptions) {
|
||||
this.optionsList.className = 'fi-select-options-ctn'
|
||||
this.optionsList.className = 'fi-select-input-options-ctn'
|
||||
} else if (optionsCount > 0) {
|
||||
// Only set fi-dropdown-list class if there are options to render
|
||||
this.optionsList.className = 'fi-dropdown-list'
|
||||
@@ -417,7 +417,7 @@ export class Select {
|
||||
}
|
||||
|
||||
const optionGroup = document.createElement('li')
|
||||
optionGroup.className = 'fi-select-option-group'
|
||||
optionGroup.className = 'fi-select-input-option-group'
|
||||
|
||||
const optionGroupLabel = document.createElement('div')
|
||||
optionGroupLabel.className = 'fi-dropdown-header'
|
||||
@@ -454,14 +454,14 @@ export class Select {
|
||||
}
|
||||
|
||||
const option = document.createElement('li')
|
||||
option.className = 'fi-dropdown-list-item fi-select-option'
|
||||
option.className = 'fi-dropdown-list-item fi-select-input-option'
|
||||
|
||||
if (isDisabled) {
|
||||
option.classList.add('fi-disabled')
|
||||
}
|
||||
|
||||
// Generate a unique ID for the option
|
||||
const optionId = `fi-select-option-${Math.random().toString(36).substring(2, 11)}`
|
||||
const optionId = `fi-select-input-option-${Math.random().toString(36).substring(2, 11)}`
|
||||
option.id = optionId
|
||||
|
||||
option.setAttribute('role', 'option')
|
||||
@@ -747,7 +747,7 @@ export class Select {
|
||||
addBadgesForSelectedOptions(selectedLabels) {
|
||||
// Create a container for the badges
|
||||
const badgesContainer = document.createElement('div')
|
||||
badgesContainer.className = 'fi-select-value-badges-ctn'
|
||||
badgesContainer.className = 'fi-select-input-value-badges-ctn'
|
||||
|
||||
// Add badges for each selected option
|
||||
selectedLabels.forEach((label, index) => {
|
||||
@@ -807,7 +807,7 @@ export class Select {
|
||||
addSingleSelectionDisplay(selectedLabel) {
|
||||
// Create a container for the label
|
||||
const labelContainer = document.createElement('span')
|
||||
labelContainer.className = 'fi-select-value-label'
|
||||
labelContainer.className = 'fi-select-input-value-label'
|
||||
|
||||
if (this.isHtmlAllowed) {
|
||||
labelContainer.innerHTML = selectedLabel
|
||||
@@ -824,7 +824,7 @@ export class Select {
|
||||
|
||||
const removeButton = document.createElement('button')
|
||||
removeButton.type = 'button'
|
||||
removeButton.className = 'fi-select-value-remove-btn'
|
||||
removeButton.className = 'fi-select-input-value-remove-btn'
|
||||
removeButton.innerHTML =
|
||||
'<svg class="fi-icon fi-size-sm" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" /></svg>'
|
||||
removeButton.setAttribute('aria-label', 'Clear selection')
|
||||
@@ -961,7 +961,7 @@ export class Select {
|
||||
// Update the displayed label
|
||||
const labelContainer =
|
||||
this.selectedDisplay.querySelector(
|
||||
'.fi-select-value-label',
|
||||
'.fi-select-input-value-label',
|
||||
)
|
||||
if (filled(labelContainer)) {
|
||||
if (this.isHtmlAllowed) {
|
||||
@@ -1486,7 +1486,7 @@ export class Select {
|
||||
// Get all option elements that are in option groups
|
||||
const groupOptions = Array.from(
|
||||
this.optionsList.querySelectorAll(
|
||||
'li.fi-select-option-group > ul > li[role="option"]',
|
||||
'li.fi-select-input-option-group > ul > li[role="option"]',
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1605,7 +1605,7 @@ export class Select {
|
||||
|
||||
// Add loading message
|
||||
const loadingItem = document.createElement('div')
|
||||
loadingItem.className = 'fi-select-message'
|
||||
loadingItem.className = 'fi-select-input-message'
|
||||
loadingItem.textContent = isSearching
|
||||
? this.searchingMessage
|
||||
: this.loadingMessage
|
||||
@@ -1614,7 +1614,7 @@ export class Select {
|
||||
|
||||
hideLoadingState() {
|
||||
// Remove loading message
|
||||
const loadingItem = this.dropdown.querySelector('.fi-select-message')
|
||||
const loadingItem = this.dropdown.querySelector('.fi-select-input-message')
|
||||
if (loadingItem) {
|
||||
loadingItem.remove()
|
||||
}
|
||||
@@ -1634,7 +1634,7 @@ export class Select {
|
||||
|
||||
// Add "No results" message
|
||||
const noResultsItem = document.createElement('div')
|
||||
noResultsItem.className = 'fi-select-message'
|
||||
noResultsItem.className = 'fi-select-input-message'
|
||||
noResultsItem.textContent = this.noSearchResultsMessage
|
||||
this.dropdown.appendChild(noResultsItem)
|
||||
}
|
||||
@@ -1775,7 +1775,7 @@ export class Select {
|
||||
|
||||
// Check if we already have a badges container
|
||||
const existingBadgesContainer = this.selectedDisplay.querySelector(
|
||||
'.fi-select-value-badges-ctn',
|
||||
'.fi-select-input-value-badges-ctn',
|
||||
)
|
||||
|
||||
if (blank(existingBadgesContainer)) {
|
||||
@@ -1912,7 +1912,7 @@ export class Select {
|
||||
// If there are remove buttons in multiple mode, disable them
|
||||
if (this.isMultiple) {
|
||||
const removeButtons = this.container.querySelectorAll(
|
||||
'.fi-select-badge-remove',
|
||||
'.fi-select-input-badge-remove',
|
||||
)
|
||||
removeButtons.forEach((button) => {
|
||||
button.setAttribute('disabled', 'disabled')
|
||||
@@ -1923,7 +1923,7 @@ export class Select {
|
||||
// If there's a remove button in single mode, disable it
|
||||
if (!this.isMultiple && this.canSelectPlaceholder) {
|
||||
const removeButton = this.container.querySelector(
|
||||
'.fi-select-value-remove-btn',
|
||||
'.fi-select-input-value-remove-btn',
|
||||
)
|
||||
if (removeButton) {
|
||||
removeButton.setAttribute('disabled', 'disabled')
|
||||
@@ -1945,7 +1945,7 @@ export class Select {
|
||||
// If there are remove buttons in multiple mode, enable them
|
||||
if (this.isMultiple) {
|
||||
const removeButtons = this.container.querySelectorAll(
|
||||
'.fi-select-badge-remove',
|
||||
'.fi-select-input-badge-remove',
|
||||
)
|
||||
removeButtons.forEach((button) => {
|
||||
button.removeAttribute('disabled')
|
||||
@@ -1956,7 +1956,7 @@ export class Select {
|
||||
// If there's a remove button in single mode, enable it
|
||||
if (!this.isMultiple && this.canSelectPlaceholder) {
|
||||
const removeButton = this.container.querySelector(
|
||||
'.fi-select-value-remove-btn',
|
||||
'.fi-select-input-value-remove-btn',
|
||||
)
|
||||
if (removeButton) {
|
||||
removeButton.removeAttribute('disabled')
|
||||
|
||||
@@ -146,7 +146,6 @@ class SupportServiceProvider extends PackageServiceProvider
|
||||
app('livewire')->componentHook(new PartialsComponentHook);
|
||||
|
||||
FilamentAsset::register([
|
||||
AlpineComponent::make('select', __DIR__ . '/../dist/components/select.js'),
|
||||
Js::make('support', __DIR__ . '/../dist/index.js'),
|
||||
], 'filament/support');
|
||||
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,12 +1,64 @@
|
||||
export default function selectTableColumn({ name, recordKey, state }) {
|
||||
import { Select } from '../../../../../support/resources/js/utilities/select.js'
|
||||
|
||||
export default function selectTableColumn({
|
||||
canSelectPlaceholder,
|
||||
isNative,
|
||||
name,
|
||||
options,
|
||||
placeholder,
|
||||
recordKey,
|
||||
state,
|
||||
}) {
|
||||
return {
|
||||
error: undefined,
|
||||
|
||||
isLoading: false,
|
||||
|
||||
select: null,
|
||||
|
||||
state,
|
||||
|
||||
init() {
|
||||
if (! isNative) {
|
||||
this.select = new Select({
|
||||
element: this.$refs.select,
|
||||
options,
|
||||
placeholder,
|
||||
state: this.state,
|
||||
// canOptionLabelsWrap,
|
||||
canSelectPlaceholder,
|
||||
// initialOptionLabel,
|
||||
// initialOptionLabels,
|
||||
// initialState,
|
||||
// isHtmlAllowed,
|
||||
// isAutofocused,
|
||||
// isDisabled,
|
||||
// isMultiple,
|
||||
// isSearchable,
|
||||
// getOptionLabelUsing,
|
||||
// getOptionLabelsUsing,
|
||||
// getOptionsUsing,
|
||||
// getSearchResultsUsing,
|
||||
// hasDynamicOptions,
|
||||
// hasDynamicSearchResults,
|
||||
// searchPrompt,
|
||||
// searchDebounce,
|
||||
// loadingMessage,
|
||||
// searchingMessage,
|
||||
// noSearchResultsMessage,
|
||||
// maxItems,
|
||||
// maxItemsMessage,
|
||||
// optionsLimit,
|
||||
// position,
|
||||
// searchableOptionFields,
|
||||
// livewireId,
|
||||
// statePath,
|
||||
onStateChange: (newState) => {
|
||||
this.state = newState
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Livewire.hook(
|
||||
'commit',
|
||||
({ component, commit, succeed, fail, respond }) => {
|
||||
@@ -40,7 +92,13 @@ export default function selectTableColumn({ name, recordKey, state }) {
|
||||
},
|
||||
)
|
||||
|
||||
this.$watch('state', async () => {
|
||||
this.$watch('state', async (newState) => {
|
||||
if ((! isNative) && this.select && this.select.state !== newState) {
|
||||
this.select.state = newState
|
||||
this.select.updateSelectedDisplay()
|
||||
this.select.renderOptions()
|
||||
}
|
||||
|
||||
const serverState = this.getServerState()
|
||||
|
||||
if (
|
||||
|
||||
@@ -3,18 +3,23 @@
|
||||
namespace Filament\Tables\Columns;
|
||||
|
||||
use BackedEnum;
|
||||
use Closure;
|
||||
use Filament\Forms\Components\Concerns\CanDisableOptions;
|
||||
use Filament\Forms\Components\Concerns\CanSelectPlaceholder;
|
||||
use Filament\Forms\Components\Concerns\HasEnum;
|
||||
use Filament\Forms\Components\Concerns\HasExtraInputAttributes;
|
||||
use Filament\Forms\Components\Concerns\HasOptions;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Support\Components\Attributes\ExposedLivewireMethod;
|
||||
use Filament\Support\Components\Contracts\HasEmbeddedView;
|
||||
use Filament\Support\Facades\FilamentAsset;
|
||||
use Filament\Tables\Columns\Contracts\Editable;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Support\Js;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
use Livewire\Attributes\Renderless;
|
||||
|
||||
class SelectColumn extends Column implements Editable, HasEmbeddedView
|
||||
{
|
||||
@@ -28,6 +33,10 @@ class SelectColumn extends Column implements Editable, HasEmbeddedView
|
||||
use HasExtraInputAttributes;
|
||||
use HasOptions;
|
||||
|
||||
protected bool | Closure $isNative = true;
|
||||
|
||||
protected ?Closure $transformOptionsForJsUsing = null;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -35,6 +44,15 @@ class SelectColumn extends Column implements Editable, HasEmbeddedView
|
||||
$this->disabledClick();
|
||||
|
||||
$this->placeholder(__('filament-forms::components.select.placeholder'));
|
||||
|
||||
$this->transformOptionsForJsUsing(static function (SelectColumn $column, array $options): array {
|
||||
return collect($options)
|
||||
->map(fn ($label, $value): array => is_array($label)
|
||||
? ['label' => $value, 'options' => $column->transformOptionsForJs($label)]
|
||||
: ['label' => $label, 'value' => strval($value), 'isDisabled' => $column->isOptionDisabled($value, $label)])
|
||||
->values()
|
||||
->all();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,6 +68,56 @@ class SelectColumn extends Column implements Editable, HasEmbeddedView
|
||||
];
|
||||
}
|
||||
|
||||
public function native(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->isNative = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isNative(): bool
|
||||
{
|
||||
return (bool) $this->evaluate($this->isNative);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array{'label': string, 'value': string}>
|
||||
*/
|
||||
#[ExposedLivewireMethod]
|
||||
#[Renderless]
|
||||
public function getOptionsForJs(): array
|
||||
{
|
||||
return $this->transformOptionsForJs($this->getOptions());
|
||||
}
|
||||
|
||||
public function transformOptionsForJsUsing(?Closure $callback): static
|
||||
{
|
||||
$this->transformOptionsForJsUsing = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string | array<string>> $options
|
||||
* @return array<array<string, mixed>>
|
||||
*/
|
||||
protected function transformOptionsForJs(array $options): array
|
||||
{
|
||||
if (empty($options)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$transformedOptions = $this->evaluate($this->transformOptionsForJsUsing, [
|
||||
'options' => $options,
|
||||
]);
|
||||
|
||||
if ($transformedOptions instanceof Arrayable) {
|
||||
return $transformedOptions->toArray();
|
||||
}
|
||||
|
||||
return $transformedOptions;
|
||||
}
|
||||
|
||||
public function toEmbeddedHtml(): string
|
||||
{
|
||||
$isDisabled = $this->isDisabled();
|
||||
@@ -60,7 +128,11 @@ class SelectColumn extends Column implements Editable, HasEmbeddedView
|
||||
'x-load' => true,
|
||||
'x-load-src' => FilamentAsset::getAlpineComponentSrc('columns/select', 'filament/tables'),
|
||||
'x-data' => 'selectTableColumn({
|
||||
canSelectPlaceholder: ' . Js::from($this->canSelectPlaceholder()) . ',
|
||||
isNative: ' . Js::from($this->isNative()) . ',
|
||||
name: ' . Js::from($this->getName()) . ',
|
||||
options: ' . Js::from($this->getOptionsForJs()) . ',
|
||||
placeholder: ' . Js::from($this->getPlaceholder()) . ',
|
||||
recordKey: ' . Js::from($this->getRecordKey()) . ',
|
||||
state: ' . Js::from($state) . ',
|
||||
})',
|
||||
@@ -109,25 +181,35 @@ class SelectColumn extends Column implements Editable, HasEmbeddedView
|
||||
}
|
||||
"
|
||||
x-on:click.prevent.stop=""
|
||||
<?php if (! $this->isNative()) { ?>
|
||||
wire:ignore
|
||||
x-on:keydown.esc="select.dropdown.isActive && $event.stopPropagation()"
|
||||
<?php } ?>
|
||||
class="fi-input-wrp"
|
||||
>
|
||||
<select
|
||||
x-model="state"
|
||||
<?= $inputAttributes->toHtml() ?>
|
||||
>
|
||||
<?php if ($this->canSelectPlaceholder()) { ?>
|
||||
<option value=""><?= $this->getPlaceholder() ?></option>
|
||||
<?php } ?>
|
||||
<?php if ($this->isNative()) { ?>
|
||||
<select
|
||||
x-model="state"
|
||||
<?= $inputAttributes->toHtml() ?>
|
||||
>
|
||||
<?php if ($this->canSelectPlaceholder()) { ?>
|
||||
<option value=""><?= $this->getPlaceholder() ?></option>
|
||||
<?php } ?>
|
||||
|
||||
<?php foreach ($this->getOptions() as $value => $label) { ?>
|
||||
<option
|
||||
<?= $this->isOptionDisabled($value, $label) ? 'disabled' : null ?>
|
||||
value="<?= $value ?>"
|
||||
>
|
||||
<?= $label ?>
|
||||
</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
<?php foreach ($this->getOptions() as $value => $label) { ?>
|
||||
<option
|
||||
<?= $this->isOptionDisabled($value, $label) ? 'disabled' : null ?>
|
||||
value="<?= $value ?>"
|
||||
>
|
||||
<?= $label ?>
|
||||
</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
<?php } else { ?>
|
||||
<div class="fi-select-input">
|
||||
<div x-ref="select"></div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user