UNPKG

@astro-utils/forms

Version:

Server component for Astro (call server functions from client side with validation and state management)

144 lines (143 loc) 5.24 kB
import { getFormValue } from '../form-tools/post.js'; import AboutFormName from './form-utils/about-form-name.js'; import { parseCheckbox, parseColor, parseDate, parseEmail, parseEmptyFiles, parseFiles, parseJSON, parseNumber, parseURL } from './form-utils/parse.js'; import { validateFunc, validateRequire, validateStringPatters } from './form-utils/validate.js'; import { getProperty } from 'dot-prop'; import { ZodType } from 'zod'; const OK_NOT_STRING_VALUE = ['checkbox', 'file']; const OK_INPUT_VALUE_NULL = ['checkbox']; const DAY_IN_MS = 86400000; export async function getInputValue(astro, bindId, bind) { const { value, name, readonly } = astro.props; if (readonly) { return getProperty(bind, name, value); } return await getFormValue(astro.request, bindId + name); } export async function validateFormInput(astro, bind, bindId) { const { type, value: originalValue, minlength, maxlength, pattern, required, name, errorMessage, validate } = astro.props; const parseValue = await getInputValue(astro, bindId, bind); const aboutInput = new AboutFormName(bind, name, parseValue, errorMessage); if (!OK_INPUT_VALUE_NULL.includes(type) && !validateRequire(aboutInput, required)) { if (type === 'file') { parseEmptyFiles(aboutInput, astro); } aboutInput.setValue(); return; } const checkStringPatterns = originalValue != null && !OK_NOT_STRING_VALUE.includes(type); if (checkStringPatterns && !validateStringPatters(aboutInput, minlength, maxlength, pattern)) { return; } await validateByInputType(astro, aboutInput, bind); if (!aboutInput.hadError) { if (typeof validate == 'function') { await validateFunc(aboutInput, validate); } else if (validate instanceof ZodType) { aboutInput.catchParse(validate); } } aboutInput.setValue(); } async function validateByInputType(astro, aboutInput, bind) { const { type, min, max, value: originalValue, multiple, readonly } = astro.props; switch (type) { case 'checkbox': parseCheckbox(aboutInput, originalValue); break; case 'color': parseColor(aboutInput); break; case 'date': case 'datetime-local': case 'month': case 'week': case 'time': parseDate(aboutInput, type, min, max); break; case 'email': parseEmail(aboutInput); break; case 'number': case 'range': case 'int': parseNumber(aboutInput, type, min, max); break; case 'radio': const plugin = bind.getPlugin('HTMLInputRadioPlugin'); plugin.addNewValue(aboutInput, originalValue); break; case 'url': parseURL(aboutInput); break; case 'json': parseJSON(aboutInput); break; case 'file': await parseFiles(aboutInput, astro, multiple, readonly); break; } } function toDateTimeLocal(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); return `${year}-${month}-${day}T${hours}:${minutes}`; } function stringifyCustomValue(date, type) { if (typeof date === 'string' || !date) { return date; } switch (type) { case 'date': return toDateTimeLocal(date).slice(0, 10); case 'datetime-local': return toDateTimeLocal(date).slice(0, 16); case 'time': return date.toTimeString().slice(0, 5); case 'month': return toDateTimeLocal(date).slice(0, 7); case 'week': return formatToDateWeek(date); case 'json': return JSON.stringify(date); } return date; } export function inputReturnValueAttr(astro, bind) { const value = stringifyCustomValue(getProperty(bind, astro.props.name, astro.props.value), astro.props.type); const min = stringifyCustomValue(astro.props.min, astro.props.type); const max = stringifyCustomValue(astro.props.max, astro.props.type); switch (astro.props.type) { case 'checkbox': return { checked: value ?? astro.props.checked }; case 'file': return {}; } return { value, min, max }; } function formatToDateWeek(date) { const year = date.getFullYear(); const firstDayOfYear = new Date(year, 0, 1); const daysSinceStartOfYear = (date.getTime() - firstDayOfYear.getTime()) / DAY_IN_MS; const weekNumber = Math.ceil((daysSinceStartOfYear + firstDayOfYear.getDay() + 1) / 7); return `${year}-W${weekNumber.toString().padStart(2, '0')}`; } export function caseTypes(type) { if (type == 'int') { return { type: 'number', pattern: '\\d+', step: '1' }; } else if (type == 'json') { return { type: 'text' }; } return { type }; }