{{-- Choosing from a list: M3's filter chips, or a searchable field with a menu. Two shapes for two jobs, both on `options` (`['id' => …, 'name' => …]`, read through `option-value` and `option-label`), `label`, `hint` and `single`: - **Chips**, the default. Every option is on screen and a press turns it on or off — "the days you are free" is seven filter chips, not a dropdown opened seven times. `single` makes them choice chips, one on at a time. - **`searchable`**, for a list too long to lay out — four hundred time zones. A text field that filters as you type, with the matches in M3's menu under it: the arrow keys move, Enter chooses, Escape puts the field back. One value only. The list is a popover in the top layer, placed by CSS anchor positioning, so a card's clipping never cuts it off. `icon`, `variant` and `placeholder` are the field's. The selection lives in Alpine, entangled with the `wire:model` property (live with `wire:model.live`), rather than in native checkboxes, which would send every value back as a string: a list of integers stays a list of integers. Without `wire:model` it works on its own and binds with `x-model`. Errors for the property and its items replace the hint. When the options change on the server, give the component a `wire:key` that changes with them: the options are baked into its Alpine state. --}} @props([ 'label' => null, 'hint' => null, 'icon' => null, 'options' => [], 'optionValue' => 'id', 'optionLabel' => 'name', 'single' => false, 'searchable' => false, 'variant' => null, 'placeholder' => null, 'value' => null, ]) @php $model = $attributes->wire('model')->value() ?: null; $messages = $model !== null && isset($errors) ? array_values(array_unique(\Illuminate\Support\Arr::flatten([$errors->get($model), $errors->get($model.'.*')]))) : []; $choices = collect($options)->map(fn ($option): array => [ 'value' => data_get($option, $optionValue), 'label' => (string) data_get($option, $optionLabel), 'disabled' => (bool) data_get($option, 'disabled', false), ])->values()->all(); $single = $single || $searchable; // Rendered as the bound property already says, so nothing flips once Alpine starts. $current = $value; if ($model !== null && ($component = \Livewire\Livewire::current()) !== null) { $current = data_get($component, $model); } $isSelected = fn ($candidate): bool => $single ? $current !== null && (string) $current === (string) $candidate : in_array((string) $candidate, array_map('strval', array_filter((array) $current, 'is_scalar')), true); $id = $attributes->get('id') ?? 'field-'.substr(md5($model.'|'.$label.'|choices'), 0, 12); $anchor = '--material-choices-'.\Illuminate\Support\Str::lower(\Illuminate\Support\Str::random(10)); @endphp @if ($searchable)