import { useRef, useState } from "react"; import { INK, PAPER, RED, FONT_MONO } from "../theme"; /* * Datum als drie getalvelden: dag / maand / jaar. Geen : de native * kiezer is gemaakt voor datums dichtbij vandaag, niet om decennia terug te bladeren * (geboortedatum). Na twee cijfers springt de focus door naar het volgende veld. * * value 'YYYY-MM-DD' of '' (alleen als startwaarde gelezen) * onChange krijgt 'YYYY-MM-DD', of null zolang de invoer onvolledig is of geen * bestaande datum vormt (31-02, 29-02-2023, …) * inputStyle extra stijl voor de velden (bijv. de formulierstijl van het scherm) * * Bereik (leeftijdsgrenzen e.d.) hoort bij de aanroeper of de server; deze component * bewaakt alleen dat het een echte kalenderdatum is. */ const isoFromParts = ({ d, m, y }) => { if (!/^\d{1,2}$/.test(d) || !/^\d{1,2}$/.test(m) || !/^\d{4}$/.test(y)) return null; const D = +d, M = +m, Y = +y; const date = new Date(Date.UTC(Y, M - 1, D)); if (date.getUTCFullYear() !== Y || date.getUTCMonth() !== M - 1 || date.getUTCDate() !== D) return null; return `${Y}-${String(M).padStart(2, "0")}-${String(D).padStart(2, "0")}`; }; const partsFromIso = (iso) => { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso ?? ""); return m ? { d: m[3], m: m[2], y: m[1] } : { d: "", m: "", y: "" }; }; const digits = (s, n) => s.replace(/\D/g, "").slice(0, n); export default function DateFields({ value, onChange, inputStyle }) { const [parts, setParts] = useState(() => partsFromIso(value)); const monthRef = useRef(null); const yearRef = useRef(null); const change = (key, max, nextRef) => (e) => { const v = digits(e.target.value, max); const next = { ...parts, [key]: v }; setParts(next); onChange(isoFromParts(next)); /* * Vol veld → door naar het volgende, en dáár de inhoud selecteren. Anders komt de cursor * achter een al ingevulde maand te staan en doet de volgende toets niets (maxLength is * bereikt): je typt tegen een vol veld aan. Met de selectie erop overschrijft de eerste * toets wat er stond, wat is wat je bedoelt als je een datum verbetert. */ if (v.length === max && nextRef?.current) { nextRef.current.focus(); nextRef.current.select(); } }; const complete = parts.d && parts.m && parts.y.length === 4; const invalid = complete && !isoFromParts(parts); const field = (w) => ({ padding: "10px 12px", fontSize: 15, border: `2px solid ${INK}`, background: PAPER, borderRadius: 0, textAlign: "center", fontVariantNumeric: "tabular-nums", ...inputStyle, width: w, }); /* * Elk veld selecteert zijn inhoud zodra het de focus krijgt — met de muis, met tab, of * doordat het vorige veld vol raakte. Deze velden hebben een vaste lengte, dus een cursor * ergens in "07" zetten helpt niemand: je wilt de maand vervangen, niet een cijfer * bijplakken tegen een vol veld aan. onMouseUp moet mee, want een klik zet ná de focus de * cursor alsnog op de aangeklikte plek en gooit de selectie weg. */ const selectAll = (e) => e.target.select(); const common = { type: "text", inputMode: "numeric", pattern: "[0-9]*", autoCorrect: "off", onFocus: selectAll, onMouseUp: (e) => e.preventDefault(), }; return (
{parts.d}-{parts.m}-{parts.y} bestaat niet
)}