99 lines
6.8 KiB
Plaintext
99 lines
6.8 KiB
Plaintext
import FieldStyles from "../styles/FieldStyles.wrn"
|
|
|
|
import ComponentBaseStyles from "../styles/ComponentBaseStyles.wrn"
|
|
|
|
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 {
|
|
<div {...attrs} class="wire-component wire-component--color-{color} wire-component--size-{size} wire-next--field wire-next--time-picker {class}" data-variant="{variant}" data-inline="{inline}" data-floating="{variant === 'floating'}" data-invalid="{error ? 'true' : 'false'}" data-expanded="{expanded}">
|
|
<div class="wire-next__field-heading"><label class="{hiddenLabel ? 'wire-next__sr-only' : ''}" for="{id || name}">{label}</label>{#if cornerHint}<span class="wire-next__field-hint">{cornerHint}</span>{/if}</div>
|
|
<div class="wire-next__field-control" data-icon-position="{iconPosition}">
|
|
{#if icon}<span class="{icon} wire-next__field-icon" aria-hidden="true"></span>{/if}
|
|
<input id="{id || name}" class="wire-next__sr-only wire-next__time-value" type="text" name="{name}" value="{currentValue}" required="{required}" readonly aria-hidden="true" tabindex="-1" @invalid="handleInvalid(event)" />
|
|
<button type="button" class="wire-next__time-trigger" disabled="{disabled || readonly}" aria-haspopup="dialog" aria-expanded="{expanded}" @click="openPicker(event)" @focus="handleFocus(event)" @blur="handleBlur(event)"><span>{currentValue || placeholder || "Select time"}</span><i class="{icon}" aria-hidden="true"></i></button>
|
|
</div>
|
|
<div class="wire-next__time-panel" role="dialog" aria-label="{label}"><div><strong>Hour</strong><div class="wire-next__time-options">{#each hours as hour}<button type="button" data-part="hour" data-value="{hour}" aria-pressed="{String(hour) === selectedHour}" @click="selectTimePart(event)">{hour}</button>{/each}</div></div><div><strong>Minute</strong><div class="wire-next__time-options">{#each minutes as minute}<button type="button" data-part="minute" data-value="{minute}" aria-pressed="{String(minute) === selectedMinute}" @click="selectTimePart(event)">{minute}</button>{/each}</div></div><button type="button" class="wire-next__time-done" @click="closePicker(event)">Done</button></div>
|
|
{#if helperText}<small class="wire-next__field-help">{helperText}</small>{/if}
|
|
<small class="wire-next__field-error" data-error="{name}">{error}</small>
|
|
</div>
|
|
<FieldStyles hidden />
|
|
<ComponentBaseStyles hidden />
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|