@oslokommune/punkt-elements
Version:
Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo
504 lines (503 loc) • 20.7 kB
JavaScript
import { c as e, d as t, i as n, l as r, o as i, r as a, s as o } from "./element-cZ85T_aa.js";
import { t as s } from "./class-map-C8HuhNzZ.js";
import { n as c } from "./element-with-slot-4J2o3SeU.js";
import "./icon-Co72KtgF.js";
import { t as l } from "./if-defined-Cjj8qN-Z.js";
import { n as u, t as d } from "./ref-RS8Uv6uC.js";
import { t as f } from "./input-element-B1ciW1N2.js";
import "./input-wrapper-DVU68NcJ.js";
//#region ../../shared-utils/timepicker/time-utils.ts
var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && parseInt(e.slice(3), 10) <= 59, m = (e) => {
let [t, n] = e.split(":").map(Number);
return t * 60 + n;
}, h = (e) => e ? Math.max(1, Math.round(e / 60)) : 1, g = (e, t) => {
let n = e ? parseInt(String(e).split(":")[0], 10) : 0, r = t ? parseInt(String(t).split(":")[0], 10) : 23;
return Array.from({ length: r - n + 1 }, (e, t) => t + n);
}, _ = (e) => {
let t = h(e);
return Array.from({ length: Math.floor(60 / t) }, (e, n) => n * t);
}, v = (e, t, n, r) => {
let i = e === "" ? n === 1 ? 0 : 23 : parseInt(e, 10), a = t === "" ? n === 1 ? 0 : Math.floor(59 / r) * r : parseInt(t, 10);
return a += n * r, a > 59 ? (a = 0, i = (i + 1) % 24) : a < 0 && (a = Math.floor(59 / r) * r, i = (i - 1 + 24) % 24), {
hours: String(i).padStart(2, "0"),
minutes: String(a).padStart(2, "0")
};
}, y = class extends f {
constructor(...e) {
super(...e), this.hiddenInputRef = d(), this.hoursInputRef = d(), this.minutesInputRef = d(), this.buttonRef = d(), this.value = "", this.hidePicker = !1, this.stepArrows = !1, this._hours = "", this._minutes = "", this._isOpen = !1, this._hoursDigitCount = 0, this._hoursFirstDigit = -1, this._minutesDigitCount = 0, this._minutesFirstDigit = -1, this._hasFocus = !1, this._outsideClickHandler = (e) => {
this.contains(e.target) || this._closePopup();
}, this._handleComponentFocusIn = () => {
this._hasFocus || (this._hasFocus = !0, this.onFocus());
}, this._handleComponentFocusOut = (e) => {
this.contains(e.relatedTarget) || (this._hasFocus = !1, this.onBlur());
}, this._handleHoursKeydown = (e) => {
let t = e.target;
switch (e.key) {
case "ArrowUp": {
e.preventDefault();
let t = this._hours === "" ? 0 : parseInt(this._hours, 10);
this._hours = String((t + 1) % 24).padStart(2, "0"), this._syncValueFromDisplay(), this.onInput();
break;
}
case "ArrowDown": {
e.preventDefault();
let t = this._hours === "" ? 0 : parseInt(this._hours, 10);
this._hours = String((t - 1 + 24) % 24).padStart(2, "0"), this._syncValueFromDisplay(), this.onInput();
break;
}
case "ArrowRight":
e.preventDefault(), this.minutesInputRef.value?.focus();
break;
case "Backspace":
case "Delete":
this._hoursDigitCount = 0, this._hoursFirstDigit = -1;
break;
case "Tab": break;
case "Enter": {
e.preventDefault();
let n = this.internals.form;
n ? n.requestSubmit() : t.blur();
break;
}
default: if (/^\d$/.test(e.key)) {
e.preventDefault();
let t = parseInt(e.key, 10);
if (this._hoursDigitCount === 0) this._hoursFirstDigit = t, this._hours = String(t).padStart(2, "0"), this._hoursDigitCount = 1, this.onInput(), t >= 3 && (this._hoursFirstDigit = -1, this._hoursDigitCount = 0, this._syncValueFromDisplay(), this.minutesInputRef.value?.focus());
else {
let e = this._hoursFirstDigit * 10 + t;
this._hours = e <= 23 ? String(e).padStart(2, "0") : String(t).padStart(2, "0"), this._hoursFirstDigit = -1, this._hoursDigitCount = 0, this._syncValueFromDisplay(), this.onInput(), this.minutesInputRef.value?.focus();
}
} else e.preventDefault();
}
}, this._handleHoursBlur = () => {
this._hours !== "" && (this._hours = String(parseInt(this._hours, 10)).padStart(2, "0")), this._hoursDigitCount = 0, this._hoursFirstDigit = -1, this._syncValueFromDisplay();
}, this._handleMinutesKeydown = (e) => {
let t = e.target;
switch (e.key) {
case "ArrowUp": {
e.preventDefault();
let t = this._minutes === "" ? 0 : parseInt(this._minutes, 10);
this._minutes = String((t + this._minuteStep) % 60).padStart(2, "0"), this._syncValueFromDisplay(), this.onInput();
break;
}
case "ArrowDown": {
e.preventDefault();
let t = this._minutes === "" ? 0 : parseInt(this._minutes, 10);
this._minutes = String((t - this._minuteStep + 60) % 60).padStart(2, "0"), this._syncValueFromDisplay(), this.onInput();
break;
}
case "ArrowLeft":
e.preventDefault(), this.hoursInputRef.value?.focus();
break;
case "Backspace":
case "Delete":
this._minutesDigitCount = 0, this._minutesFirstDigit = -1;
break;
case "Tab": break;
case "Enter": {
e.preventDefault();
let n = this.internals.form;
n ? n.requestSubmit() : t.blur();
break;
}
default: if (/^\d$/.test(e.key)) {
e.preventDefault();
let t = parseInt(e.key, 10);
if (this._minutesDigitCount === 0) this._minutesFirstDigit = t, this._minutes = String(t).padStart(2, "0"), this._minutesDigitCount = 1, this.onInput(), t >= 6 && (this._minutesFirstDigit = -1, this._minutesDigitCount = 0, this._syncValueFromDisplay());
else {
let e = this._minutesFirstDigit * 10 + t;
this._minutes = e <= 59 ? String(e).padStart(2, "0") : String(t).padStart(2, "0"), this._minutesFirstDigit = -1, this._minutesDigitCount = 0, this._syncValueFromDisplay(), this.onInput();
}
} else e.preventDefault();
}
}, this._handleMinutesBlur = () => {
this._minutes !== "" && (this._minutes = String(parseInt(this._minutes, 10)).padStart(2, "0")), this._minutesDigitCount = 0, this._minutesFirstDigit = -1, this._syncValueFromDisplay();
}, this._handlePopupKeydown = (e) => {
let t = document.activeElement, n = t.closest(".pkt-timepicker-popup__col");
if (!n) return;
let r = Array.from(n.querySelectorAll(".pkt-timepicker-popup__option")), i = r.indexOf(t), a = t.dataset.type ?? "";
switch (e.key) {
case "ArrowDown":
e.preventDefault(), this._focusOptionAndSync(r[Math.min(i + 1, r.length - 1)], a);
break;
case "ArrowUp":
e.preventDefault(), this._focusOptionAndSync(r[Math.max(i - 1, 0)], a);
break;
case "Home":
e.preventDefault(), this._focusOptionAndSync(r[0], a);
break;
case "End":
e.preventDefault(), this._focusOptionAndSync(r[r.length - 1], a);
break;
case "ArrowRight":
e.preventDefault(), a === "hour" && (this._focusOptionAndSync(t, a), this.updateComplete.then(() => {
this._scrollToSelected(), this._focusSelectedOrFirst("minute");
}));
break;
case "ArrowLeft":
e.preventDefault(), a === "minute" && (this._focusOptionAndSync(t, a), this.updateComplete.then(() => {
this._scrollToSelected(), this._focusSelectedOrFirst("hour");
}));
break;
case "Enter":
case " ":
e.preventDefault(), t.click();
break;
case "Escape":
e.preventDefault(), this._closePopup(), this.buttonRef.value?.focus();
break;
}
};
}
get inputRef() {
return this.hiddenInputRef;
}
manageValidity(e) {
let t = this.hoursInputRef.value;
if (!t) return;
if (this.required && !this.value) {
this.internals.setValidity({ valueMissing: !0 }, n.forms.messages.required, t), this._setAriaInvalid(!0);
return;
}
if (!this.value) {
this.internals.setValidity({}), this._setAriaInvalid(!1);
return;
}
let r = m(this.value), i = h(this.step);
if (this.min && r < m(String(this.min))) {
this.internals.setValidity({ rangeUnderflow: !0 }, n.forms.messages.rangeUnderflowMin.replace("{min}", String(this.min)), t), this._setAriaInvalid(!0);
return;
}
if (this.max && r > m(String(this.max))) {
this.internals.setValidity({ rangeOverflow: !0 }, n.forms.messages.rangeOverflowMax.replace("{max}", String(this.max)), t), this._setAriaInvalid(!0);
return;
}
if (this.step && r % i !== 0) {
let e = i === 60 ? n.forms.messages.timeStepMismatchHour : i === 30 ? n.forms.messages.timeStepMismatchHalfHour : n.forms.messages.timeStepMismatch.replace("{step}", `${i}, ${i * 2}, ${i * 3}`);
this.internals.setValidity({ stepMismatch: !0 }, e, t), this._setAriaInvalid(!0);
return;
}
this.internals.setValidity({}), this._setAriaInvalid(!1);
}
_setAriaInvalid(e) {
if (!this.touched && e) return;
let t = this.hoursInputRef.value, n = this.minutesInputRef.value;
e ? (t?.setAttribute("aria-invalid", "true"), n?.setAttribute("aria-invalid", "true")) : (t?.removeAttribute("aria-invalid"), n?.removeAttribute("aria-invalid"));
}
connectedCallback() {
super.connectedCallback(), this.addEventListener("focusin", this._handleComponentFocusIn), this.addEventListener("focusout", this._handleComponentFocusOut), document.addEventListener("click", this._outsideClickHandler);
}
disconnectedCallback() {
super.disconnectedCallback(), this.removeEventListener("focusin", this._handleComponentFocusIn), this.removeEventListener("focusout", this._handleComponentFocusOut), document.removeEventListener("click", this._outsideClickHandler);
}
willUpdate(e) {
super.willUpdate(e), e.has("value") && this._syncDisplayFromValue();
}
updated(e) {
super.updated(e), this.classList.toggle("pkt-timepicker--stepper", this.stepArrows), this.classList.toggle("pkt-timepicker--fullwidth", this.fullwidth), e.has("step") && this.step !== null && !this._isValidStep(this.step) && console.warn(`pkt-timepicker: step="${this.step}" er ikke en gyldig verdi. Step må være et multiplum av 60 (hele minutter) eller nøyaktig 3600 (hel time).`);
}
formResetCallback() {
super.formResetCallback(), this._hours = "", this._minutes = "", this._isOpen = !1;
}
_isValidStep(e) {
return e === 3600 || e < 3600 && 3600 % e == 0 && e % 60 == 0;
}
_syncDisplayFromValue() {
let e = p(this.value) ? this.value.split(":") : null;
e ? (this._hours = e[0], this._minutes = e[1]) : (this._hours = "", this._minutes = "");
}
_syncValueFromDisplay() {
if (this._hours !== "" && this._minutes !== "") {
let e = `${this._hours}:${this._minutes}`;
e !== this.value && (this.value = e, this.onChange(e));
} else this.value !== "" && (this.value = "", this.onChange(""));
}
get _minuteStep() {
return h(this.step);
}
get _hourOptions() {
return g(this.min, this.max);
}
get _minuteOptions() {
return _(this.step);
}
_openPopup() {
this._isOpen = !0, this.updateComplete.then(() => {
this._scrollToSelected(), this._focusSelectedOrFirst("hour");
});
}
_closePopup() {
this._isOpen = !1, this._syncValueFromDisplay();
}
_togglePopup() {
this._isOpen ? this._closePopup() : this._openPopup();
}
_scrollToSelected() {
let e = this.querySelector(".pkt-timepicker-popup");
e && e.querySelectorAll(".pkt-timepicker-popup__col").forEach((e) => {
let t = e.querySelector(".pkt-timepicker-popup__option--selected");
t && t.scrollIntoView({ block: "center" });
});
}
_focusSelectedOrFirst(e) {
let t = this.querySelector(".pkt-timepicker-popup");
if (!t) return;
let n = t.querySelectorAll(".pkt-timepicker-popup__col"), r = e === "hour" ? n[0] : n[1];
if (!r) return;
let i = r.querySelector(".pkt-timepicker-popup__option--selected"), a = r.querySelector(".pkt-timepicker-popup__option");
(i || a)?.focus();
}
_handleOptionClick(e, t) {
let n = String(e).padStart(2, "0");
t === "hour" ? (this._hours = n, this.updateComplete.then(() => this._focusSelectedOrFirst("minute"))) : (this._minutes = n, this._closePopup(), this.buttonRef.value?.focus());
}
_focusOptionAndSync(e, t) {
if (!e) return;
let n = parseInt(e.dataset.value ?? "0", 10);
t === "hour" ? this._hours = String(n).padStart(2, "0") : this._minutes = String(n).padStart(2, "0"), e.focus();
}
_stepTime(e) {
let t = v(this._hours, this._minutes, e, this._minuteStep);
this._hours = t.hours, this._minutes = t.minutes, this._syncValueFromDisplay();
}
_renderOption(e, n) {
let r = String(e).padStart(2, "0"), i = e === (n === "hour" ? this._hours === "" ? NaN : parseInt(this._hours, 10) : this._minutes === "" ? NaN : parseInt(this._minutes, 10));
return t`
<button
class=${s({
"pkt-btn": !0,
"pkt-btn--tertiary": !0,
"pkt-btn--small": !0,
"pkt-btn--label-only": !0,
"pkt-timepicker-popup__option": !0,
"pkt-timepicker-popup__option--selected": i
})}
type="button"
role="option"
aria-selected=${i ? "true" : "false"}
tabindex=${i ? "0" : "-1"}
data-type=${n}
data-value=${e}
=${(t) => {
t.stopImmediatePropagation(), this._handleOptionClick(e, n);
}}
>
<span class="pkt-btn__text pkt-txt-14-light">${r}</span>
</button>
`;
}
_renderPopup() {
return t`
<div
class="pkt-timepicker-popup"
id=${this.id + "-popup"}
?hidden=${!this._isOpen}
role="group"
aria-label=${this.strings.timepicker?.selectTime ?? "Velg tidspunkt"}
=${this._handlePopupKeydown}
=${(e) => {
this.querySelector(".pkt-timepicker-popup")?.contains(e.relatedTarget) || this._closePopup();
}}
>
<div
class="pkt-timepicker-popup__col"
role="listbox"
aria-label=${this.strings.timepicker?.hours ?? "Timer"}
aria-orientation="vertical"
>
${this._hourOptions.map((e) => this._renderOption(e, "hour"))}
</div>
<div
class="pkt-timepicker-popup__col"
role="listbox"
aria-label=${this.strings.timepicker?.minutes ?? "Minutter"}
aria-orientation="vertical"
>
${this._minuteOptions.map((e) => this._renderOption(e, "minute"))}
</div>
</div>
`;
}
_renderContainer() {
let e = this.strings.timepicker?.hours ?? "Timer", n = this.strings.timepicker?.minutes ?? "Minutter", i = this.label ? `${e}, ${this.label}` : e, a = this.label ? `${n}, ${this.label}` : n;
return t`
<div
class="pkt-input__container"
=${(e) => {
e.target.closest("button, input") || this.hoursInputRef.value?.focus();
}}
>
${this.stepArrows ? t`
<button
class="pkt-input-icon pkt-btn pkt-btn--icon-only pkt-btn--tertiary pkt-timepicker__button pkt-timepicker__button--prev"
type="button"
aria-label=${this.strings.timepicker?.prevTime ?? "Forrige tidspunkt"}
?disabled=${this.disabled}
=${() => this._stepTime(-1)}
>
<pkt-icon name="chevron-thin-left"></pkt-icon>
<span class="pkt-btn__text"
>${this.strings.timepicker?.prevTime ?? "Forrige tidspunkt"}</span
>
</button>
` : r}
<input
${u(this.hoursInputRef)}
type="text"
inputmode="numeric"
maxlength="2"
size="2"
class="pkt-input pkt-timepicker__input"
id=${this.id + "-hours"}
data-min="0"
data-max="23"
.value=${this._hours}
placeholder="––"
aria-label=${i}
role="spinbutton"
aria-invalid=${this.hasError ? "true" : r}
aria-valuemin="0"
aria-valuemax="23"
aria-valuenow=${this._hours === "" ? r : this._hours}
aria-valuetext=${this._hours === "" ? r : `${this._hours} ${e.toLowerCase()}`}
autocomplete="off"
?disabled=${this.disabled}
=${this._handleHoursKeydown}
=${this._handleHoursBlur}
=${(e) => {
e.target.select(), e.stopImmediatePropagation();
}}
/>
<span class="pkt-timepicker__separator">:</span>
<input
${u(this.minutesInputRef)}
type="text"
inputmode="numeric"
maxlength="2"
size="2"
class="pkt-input pkt-timepicker__input"
id=${this.id + "-minutes"}
data-min="0"
data-max="59"
.value=${this._minutes}
placeholder="––"
aria-label=${a}
role="spinbutton"
aria-invalid=${this.hasError ? "true" : r}
aria-valuemin="0"
aria-valuemax="59"
aria-valuenow=${this._minutes === "" ? r : this._minutes}
aria-valuetext=${this._minutes === "" ? r : `${this._minutes} ${n.toLowerCase()}`}
autocomplete="off"
?disabled=${this.disabled}
=${this._handleMinutesKeydown}
=${this._handleMinutesBlur}
=${(e) => {
e.target.select(), e.stopImmediatePropagation();
}}
/>
${this.hidePicker && !this.stepArrows ? t`<pkt-icon
class="pkt-input-icon pkt-timepicker__icon"
name="clock"
aria-hidden="true"
></pkt-icon>` : r}
${!this.hidePicker && !this.stepArrows ? t`
<button
${u(this.buttonRef)}
class="pkt-input-icon pkt-btn pkt-btn--icon-only pkt-btn--tertiary pkt-timepicker__button"
type="button"
aria-label=${this.strings.timepicker?.openPicker ?? "Åpne tidspunkt-velger"}
aria-haspopup="listbox"
aria-expanded=${this._isOpen ? "true" : "false"}
aria-controls=${this.id + "-popup"}
?disabled=${this.disabled}
=${this._togglePopup}
>
<pkt-icon name="clock"></pkt-icon>
<span class="pkt-btn__text"
>${this.strings.timepicker?.openPicker ?? "Åpne tidspunkt-velger"}</span
>
</button>
` : r}
${this.stepArrows ? t`
<button
class="pkt-input-icon pkt-btn pkt-btn--icon-only pkt-btn--tertiary pkt-timepicker__button pkt-timepicker__button--next"
type="button"
aria-label=${this.strings.timepicker?.nextTime ?? "Neste tidspunkt"}
?disabled=${this.disabled}
=${() => this._stepTime(1)}
>
<pkt-icon name="chevron-thin-right"></pkt-icon>
<span class="pkt-btn__text"
>${this.strings.timepicker?.nextTime ?? "Neste tidspunkt"}</span
>
</button>
` : r}
<input
${u(this.hiddenInputRef)}
type="time"
hidden
name=${this.name || this.id}
id=${this.id + "-input"}
.value=${this.value}
min=${l(this.min ?? void 0)}
max=${l(this.max ?? void 0)}
step=${l(this.step ?? void 0)}
?required=${this.required}
?disabled=${this.disabled}
tabindex="-1"
/>
</div>
`;
}
render() {
return t`
<pkt-input-wrapper
label="${this.label}"
?disabled=${this.disabled}
?fullwidth=${this.fullwidth}
?hasError=${this.hasError}
?inline=${this.inline}
?optionalTag=${this.optionalTag}
?required=${this.required}
?requiredTag=${this.requiredTag}
useWrapper=${this.useWrapper}
.ariaDescribedBy=${this.ariaDescribedBy}
.errorMessage=${this.errorMessage}
.forId="${this.id + "-hours"}"
.helptext=${this.helptext}
.helptextDropdown=${this.helptextDropdown}
.helptextDropdownButton=${this.helptextDropdownButton}
.optionalText=${this.optionalText}
.requiredText=${this.requiredText}
.tagText=${this.tagText}
>
<div class="pkt-contents" slot="helptext">${c(this, "helptext")}</div>
${!this.hidePicker && !this.stepArrows ? t`
<div class="pkt-timepicker__anchor">
${this._renderContainer()} ${this._renderPopup()}
</div>
` : this._renderContainer()}
</pkt-input-wrapper>
`;
}
};
a([o({
type: String,
reflect: !0
})], y.prototype, "value", void 0), a([o({
type: Boolean,
reflect: !0,
attribute: "hide-picker"
})], y.prototype, "hidePicker", void 0), a([o({
type: Boolean,
reflect: !0,
attribute: "step-arrows"
})], y.prototype, "stepArrows", void 0), a([i()], y.prototype, "_hours", void 0), a([i()], y.prototype, "_minutes", void 0), a([i()], y.prototype, "_isOpen", void 0);
try {
e("pkt-timepicker")(y);
} catch {
console.warn("Forsøker å definere <pkt-timepicker>, men den er allerede definert");
}
//#endregion
export { y as t };