@props([
'name',
'id',
'options' => [],
'value' => null,
'required' => false,
'error' => false,
'emptyOption' => true,
'emptyLabel' => '',
'placeholder' => 'ابحث بالاسم أو الكود...',
'inModal' => false,
/** لو true: قائمة منسدلة position:fixed لتفادي القص داخل overflow أو نافذة منبثقة */
'fixedPanel' => false,
/** عند true: لا يُصدَر input مخفي؛ ضع حقلاً مخفياً في الأب مع x-model (نماذج Alpine متداخلة) */
'omitHidden' => false,
/** عند false: إخفاء حقل البحث داخل اللوحة (قوائم قصيرة مثل الحالة) */
'searchable' => true,
])
@php
$selectedRaw = old($name, $value);
$selectedStr = $selectedRaw === null || $selectedRaw === '' ? '' : (string) $selectedRaw;
$id = filled($id ?? null)
? (string) $id
: 'erp-ss-'.preg_replace('/[^a-zA-Z0-9_-]+/', '-', (string) $name).'-'.substr(uniqid('', true), -8);
$wireAttrs = $attributes->filter(fn ($v, $k) => is_string($k) && str_starts_with($k, 'wire:model'));
$outerAttrs = $attributes->except(array_keys($wireAttrs->getAttributes()));
if ($omitHidden) {
$outerAttrs = $outerAttrs->merge($wireAttrs->getAttributes());
}
$normalized = [];
foreach ($options as $row) {
if (is_array($row)) {
$normalized[] = [
'v' => (string) ($row['value'] ?? ''),
'l' => (string) ($row['label'] ?? ''),
];
} elseif (is_object($row)) {
$normalized[] = [
'v' => (string) ($row->value ?? ''),
'l' => (string) ($row->label ?? ''),
];
}
}
if ($emptyOption) {
array_unshift($normalized, [
'v' => '',
'l' => $emptyLabel !== '' ? $emptyLabel : '—',
]);
}
$panelZ = $inModal ? 'z-[1060]' : 'z-40';
$useFixed = $fixedPanel || $inModal;
$panelPositionClass = $useFixed ? 'fixed' : 'absolute mt-1';
$borderClass = $error ? 'border-red-500 ring-1 ring-red-200' : 'border-gray-200';
@endphp
class(['erp-searchable-select', 'relative', 'w-full']) }}
x-data="{
open: false,
q: '',
items: @js($normalized),
selected: @js($selectedStr),
placeholder: @js($placeholder),
useFixed: @js($useFixed),
panelTop: 0,
panelLeft: 0,
panelWidth: 0,
positionPanel() {
if (!this.useFixed) return;
const btn = this.$refs.triggerBtn;
const panel = this.$refs.dropdownPanel;
if (!btn || !panel) return;
const r = btn.getBoundingClientRect();
const width = Math.max(r.width, 160);
const maxLeft = Math.max(8, window.innerWidth - width - 8);
this.panelWidth = width;
this.panelTop = r.bottom + 4;
this.panelLeft = Math.min(Math.max(8, r.left), maxLeft);
},
toggle() {
this.open = !this.open;
if (this.open) {
this.$nextTick(() => {
this.positionPanel();
if (this.$refs.q) { this.$refs.q.focus(); }
});
}
},
close() {
this.open = false;
this.q = '';
},
pick(v) {
this.selected = v;
this.close();
const detail = { name: @js($name), value: v };
this.$dispatch('searchable-select-change', detail);
this.$dispatch('custom-select-change', detail);
this.$nextTick(() => {
const h = this.$refs.hiddenInput;
if (h) {
h.dispatchEvent(new Event('input', { bubbles: true }));
h.dispatchEvent(new Event('change', { bubbles: true }));
}
});
},
clearSel() {
this.selected = '';
this.q = '';
const detail = { name: @js($name), value: '' };
this.$dispatch('searchable-select-change', detail);
this.$dispatch('custom-select-change', detail);
this.$nextTick(() => {
const h = this.$refs.hiddenInput;
if (h) {
h.dispatchEvent(new Event('input', { bubbles: true }));
h.dispatchEvent(new Event('change', { bubbles: true }));
}
});
},
display() {
const it = this.items.find(i => String(i.v) === String(this.selected));
return it ? it.l : @js($emptyLabel !== '' ? $emptyLabel : '—');
},
get filtered() {
const qq = (this.q || '').trim().toLowerCase();
if (!qq) {
return this.items;
}
return this.items.filter(i => {
if (i.v === '') {
return true;
}
const lab = (i.l || '').toLowerCase();
const val = (i.v || '').toLowerCase();
return lab.includes(qq) || val.includes(qq);
});
},
}"
@if($useFixed)
@resize.window="if (open) positionPanel()"
@scroll.window.passive="if (open) positionPanel()"
@endif
@if(! $omitHidden)
x-modelable="selected"
@endif
@erp-sync-searchable.window="if ($event.detail && String($event.detail.id) === @js($id)) { selected = $event.detail.value != null && $event.detail.value !== '' ? String($event.detail.value) : ''; }"
@click.outside="close()"
>
@unless($omitHidden)
@endunless
@if($searchable)
@endif
@if($emptyOption)
@endif