component TimePicker { outputs { input(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]) change(payload: { sourceEvent?: Event; value: string | number | boolean | null | object; [key: string]: string | number | boolean | null | object } | string | number | boolean | null | object[]) focus(payload: { value: string; name: string; sourceEvent: Event }) blur(payload: { value: string; name: string; sourceEvent: Event }) open(payload: { value: string; name: string; sourceEvent: Event }) close(payload: { value: string; name: string; sourceEvent: Event }) invalid(payload: { name: string; message: string; sourceEvent: Event }) } props { size: string = "default" color: string = "primary" id: string = "" name: string = "" label: string = "Time" hiddenLabel: boolean = false value: string = "" placeholder: string = "" variant: string = "normal" icon: string = "icon-[lucide--clock-3]" iconPosition: string = "end" helperText: string = "" cornerHint: string = "" error: string = "" inline: boolean = false min: string = "" max: string = "" step: string = "" format: string = "24" minuteStep: number = 5 hours = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"] minutes = ["00", "05", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55"] readonly: boolean = false disabled: boolean = false required: boolean = false class: string = "" } state currentValue = value state selectedHour = value ? value.split(":")[0] : "00" state selectedMinute = value ? value.split(":")[1] : "00" state expanded: boolean = false functions { client function commit(part, nextValue, sourceEvent, next, detail) { if (part === "hour") { selectedHour = String(nextValue).padStart(2, "0") } if (part === "minute") { selectedMinute = String(nextValue).padStart(2, "0") } next = selectedHour + ":" + selectedMinute; if ((min && next < min) || (max && next > max)) { return } currentValue = next; detail = { value: currentValue, hour: selectedHour, minute: selectedMinute, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) } client function selectTimePart(sourceEvent, root, input, parts, hourValue, minuteValue, next, trigger, detail) { root = sourceEvent.currentTarget.closest(".wire-next--time-picker"); input = root.querySelector(".wire-next__time-value"); parts = String(input.value || value || "00:00").split(":"); hourValue = sourceEvent.currentTarget.dataset.part === "hour" ? sourceEvent.currentTarget.dataset.value : parts[0]; minuteValue = sourceEvent.currentTarget.dataset.part === "minute" ? sourceEvent.currentTarget.dataset.value : parts[1]; next = hourValue + ":" + minuteValue; if ((min && next < min) || (max && next > max)) { return } currentValue = next; input.setAttribute("value", next); trigger = root.querySelector(".wire-next__time-trigger span"); if (trigger) { trigger.replaceChildren(next) } sourceEvent.currentTarget.setAttribute("aria-pressed", "true"); detail = { value: next, hour: hourValue, minute: minuteValue, name: name, sourceEvent: sourceEvent }; output.input(detail); output.change(detail) } client function openPicker(sourceEvent) { expanded = true; output.open({ value: currentValue, name: name, sourceEvent: sourceEvent }) } client function closePicker(sourceEvent) { expanded = false; output.close({ value: currentValue, name: name, sourceEvent: sourceEvent }) } client function handleFocus(sourceEvent) { output.focus({ value: currentValue, name: name, sourceEvent: sourceEvent }) } client function handleBlur(sourceEvent) { output.blur({ value: currentValue, name: name, sourceEvent: sourceEvent }) } client function handleInvalid(sourceEvent) { output.invalid({ name: name, message: sourceEvent.currentTarget.validationMessage, sourceEvent: sourceEvent }) } } view {
} style { .wire-next__time-panel { grid-template-columns: 1fr 1fr; } .wire-next__time-options { display: grid; max-height: 12rem; margin-top: 0.4rem; overflow-y: auto; gap: 0.2rem; } .wire-next__time-done { align-self: end; background: var(--wire-field-color) !important; color: var(--wire-color-primary-contrast, #fff) !important; } @media (max-width: 640px) { .wire-next__time-panel { grid-template-columns: 1fr; } } } }