2026-01-11 02:48:05 +00:00
|
|
|
@php
|
|
|
|
|
$inputId = $htmlId !== 'null' ? $htmlId . '-input' : null;
|
|
|
|
|
$selectId = $htmlId !== 'null' ? $htmlId . '-select' : null;
|
|
|
|
|
@endphp
|
|
|
|
|
|
2026-01-11 05:18:28 +00:00
|
|
|
<style>
|
|
|
|
|
/* Apply dirty styling to visible input when hidden input is dirty */
|
|
|
|
|
.input-with-select-container input[type="hidden"].dirty-tracker ~ input {
|
|
|
|
|
box-shadow: inset 4px 0 0 #6b16ed, inset 0 0 0 2px #e5e5e5 !important;
|
|
|
|
|
}
|
|
|
|
|
.dark .input-with-select-container input[type="hidden"].dirty-tracker ~ input {
|
|
|
|
|
box-shadow: inset 4px 0 0 #fcd452, inset 0 0 0 2px #242424 !important;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
2026-01-11 17:28:40 +00:00
|
|
|
<div class="w-full"
|
2026-01-11 02:48:05 +00:00
|
|
|
x-data="inputWithSelect({
|
|
|
|
|
defaultUnit: @js($defaultOption ?? ''),
|
|
|
|
|
min: @js($min),
|
|
|
|
|
max: @js($max),
|
2026-01-11 03:26:39 +00:00
|
|
|
validUnits: @js(array_keys($options)),
|
2026-01-11 02:48:05 +00:00
|
|
|
@if ($modelBinding !== 'null')
|
2026-01-11 17:28:40 +00:00
|
|
|
entangled: @entangle($combinedBinding),
|
2026-01-11 02:48:05 +00:00
|
|
|
@else
|
2026-01-11 17:28:40 +00:00
|
|
|
entangled: @js($value ?? '0'),
|
2026-01-11 02:48:05 +00:00
|
|
|
@endif
|
|
|
|
|
})"
|
|
|
|
|
x-ref="container">
|
|
|
|
|
@if ($label)
|
|
|
|
|
<label @if ($inputId) for="{{ $inputId }}" @endif class="flex gap-1 items-center mb-1 text-sm font-medium">
|
|
|
|
|
{{ $label }}
|
|
|
|
|
@if ($required)
|
|
|
|
|
<x-highlighted text="*" />
|
|
|
|
|
@endif
|
|
|
|
|
@if ($helper)
|
|
|
|
|
<x-helper :helper="$helper" />
|
|
|
|
|
@endif
|
|
|
|
|
</label>
|
|
|
|
|
@endif
|
|
|
|
|
|
2026-01-11 05:18:28 +00:00
|
|
|
<div class="flex input-with-select-container">
|
|
|
|
|
{{-- Hidden input for wire:dirty tracking (binds to combinedValue which has the full value with unit) --}}
|
|
|
|
|
@if ($modelBinding !== 'null')
|
2026-01-11 17:28:40 +00:00
|
|
|
<input type="hidden"
|
2026-01-11 05:18:28 +00:00
|
|
|
wire:model={{ $combinedBinding }}
|
|
|
|
|
wire:dirty.class="dirty-tracker"
|
|
|
|
|
/>
|
|
|
|
|
@endif
|
2026-01-11 17:28:40 +00:00
|
|
|
|
2026-01-11 02:48:05 +00:00
|
|
|
{{-- Input --}}
|
2026-01-11 17:28:40 +00:00
|
|
|
<input
|
2026-01-11 02:48:05 +00:00
|
|
|
type="{{ $type }}"
|
|
|
|
|
@if ($inputId) id="{{ $inputId }}" @endif
|
2026-01-11 17:28:40 +00:00
|
|
|
x-model="value"
|
|
|
|
|
@blur="commit()"
|
2026-01-11 02:48:05 +00:00
|
|
|
@disabled($disabled)
|
|
|
|
|
@readonly($readonly)
|
|
|
|
|
placeholder="{{ $placeholder }}"
|
|
|
|
|
autocomplete="{{ $autocomplete }}"
|
|
|
|
|
name="{{ $name }}-input"
|
|
|
|
|
@if ($min !== null) min="{{ $min }}" @endif
|
|
|
|
|
@if ($max !== null) max="{{ $max }}" @endif
|
|
|
|
|
minlength="{{ $minlength }}"
|
|
|
|
|
maxlength="{{ $maxlength }}"
|
|
|
|
|
class="{{ $defaultClass }} rounded-r-none flex-1 border-r-0"
|
|
|
|
|
@if ($autofocus) x-ref="autofocusInput" @endif
|
|
|
|
|
aria-label="{{ $label }}"
|
|
|
|
|
x-ref="input"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{{-- Select --}}
|
2026-01-11 17:28:40 +00:00
|
|
|
<select
|
2026-01-11 02:48:05 +00:00
|
|
|
@if ($selectId) id="{{ $selectId }}" @endif
|
2026-01-11 17:28:40 +00:00
|
|
|
x-model="unit"
|
|
|
|
|
@change="commit()"
|
2026-01-11 02:48:05 +00:00
|
|
|
@disabled($disabled)
|
|
|
|
|
name="{{ $name }}-select"
|
|
|
|
|
class="select rounded-l-none w-auto min-w-[70px] border-l-0"
|
|
|
|
|
aria-label="{{ $label }} unit"
|
|
|
|
|
>
|
|
|
|
|
@foreach($options as $key => $display)
|
|
|
|
|
<option value="{{ $key }}">{{ $display }}</option>
|
|
|
|
|
@endforeach
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@if (!$label && $helper)
|
|
|
|
|
<x-helper :helper="$helper" />
|
|
|
|
|
@endif
|
|
|
|
|
@error($modelBinding)
|
|
|
|
|
<label class="label">
|
|
|
|
|
<span class="text-red-500 label-text-alt">{{ $message }}</span>
|
|
|
|
|
</label>
|
|
|
|
|
@enderror
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
2026-01-11 17:28:40 +00:00
|
|
|
(function() {
|
|
|
|
|
let registered = false;
|
|
|
|
|
|
|
|
|
|
function registerInputWithSelect() {
|
|
|
|
|
// Prevent duplicate registration
|
|
|
|
|
if (registered) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Alpine.data('inputWithSelect', ({ defaultUnit, validUnits, entangled }) => ({
|
|
|
|
|
value: '',
|
|
|
|
|
unit: defaultUnit,
|
|
|
|
|
entangled: entangled,
|
|
|
|
|
|
|
|
|
|
init() {
|
|
|
|
|
this.fromCombined(this.entangled);
|
|
|
|
|
|
|
|
|
|
// Watch for external changes from Livewire
|
|
|
|
|
this.$watch('entangled', (newVal) => {
|
|
|
|
|
const current = this.combined;
|
|
|
|
|
if (newVal !== current) {
|
|
|
|
|
this.fromCombined(newVal);
|
2026-01-11 03:26:39 +00:00
|
|
|
}
|
2026-01-11 17:28:40 +00:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
get combined() {
|
|
|
|
|
return this.value ? this.value + this.unit : '0';
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
commit() {
|
|
|
|
|
this.entangled = this.combined;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fromCombined(raw) {
|
|
|
|
|
if (!raw || raw === '0' || raw === 'null' || raw === null) {
|
|
|
|
|
this.value = '';
|
|
|
|
|
this.unit = defaultUnit;
|
|
|
|
|
return;
|
2026-01-11 03:26:39 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-11 17:28:40 +00:00
|
|
|
const units = [...validUnits].sort((a, b) => b.length - a.length);
|
|
|
|
|
for (const u of units) {
|
|
|
|
|
if (raw.endsWith(u)) {
|
|
|
|
|
const val = raw.slice(0, -u.length);
|
|
|
|
|
if (val) {
|
|
|
|
|
this.value = val;
|
|
|
|
|
this.unit = u;
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-01-11 03:26:39 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-11 17:28:40 +00:00
|
|
|
this.value = raw;
|
|
|
|
|
this.unit = defaultUnit;
|
2026-01-11 03:26:39 +00:00
|
|
|
}
|
2026-01-11 17:28:40 +00:00
|
|
|
}));
|
2026-01-11 03:26:39 +00:00
|
|
|
|
2026-01-11 17:28:40 +00:00
|
|
|
registered = true;
|
|
|
|
|
}
|
2026-01-11 02:48:05 +00:00
|
|
|
|
2026-01-11 17:28:40 +00:00
|
|
|
// Alpine already initialized (SPA navigation) - register immediately
|
|
|
|
|
if (window.Alpine) {
|
|
|
|
|
registerInputWithSelect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Also listen for alpine:init (initial page load)
|
|
|
|
|
document.addEventListener('alpine:init', registerInputWithSelect);
|
|
|
|
|
})();
|
2026-01-11 02:48:05 +00:00
|
|
|
</script>
|