UNPKG

neumorphic-peripheral

Version:

A lightweight, framework-agnostic JavaScript/TypeScript library for beautiful neumorphic styling

452 lines (444 loc) 16.4 kB
"use strict"; /** * Yup Validation Adapter for Neumorphic Peripheral * * This adapter allows you to use Yup schemas for validation * Install: npm install yup * * Usage: * import * as yup from 'yup' * import { yupAdapter } from 'neumorphic-peripheral/adapters/yup' * * const schema = yup.string().email().min(5) * np.input(element, { validate: yupAdapter(schema) }) */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.examples = exports.yupUtils = exports.yupIntegration = exports.YupFormValidator = exports.yupSchemas = void 0; exports.yupAdapter = yupAdapter; exports.yupAdapterDetailed = yupAdapterDetailed; exports.yupAdapterAsync = yupAdapterAsync; exports.createYupFormValidator = createYupFormValidator; /** * Creates a validation function from a Yup schema */ function yupAdapter(schema) { return (value) => { try { schema.validateSync(value); return null; // Valid } catch (error) { // Return first error message from Yup return error.message || 'Invalid value'; } }; } /** * Creates a comprehensive validation function that returns all errors */ function yupAdapterDetailed(schema) { return (value) => { try { schema.validateSync(value, { abortEarly: false }); return { isValid: true, errors: [] }; } catch (error) { const errors = []; if (error.inner && Array.isArray(error.inner)) { errors.push(...error.inner.map((err) => err.message)); } else if (error.message) { errors.push(error.message); } else { errors.push('Invalid value'); } return { isValid: false, errors }; } }; } /** * Async validation adapter for Yup schemas */ function yupAdapterAsync(schema) { return async (value) => { try { await schema.validate(value, { abortEarly: false }); return { isValid: true, errors: [] }; } catch (error) { const errors = []; if (error.inner && Array.isArray(error.inner)) { errors.push(...error.inner.map((err) => err.message)); } else if (error.message) { errors.push(error.message); } else { errors.push('Invalid value'); } return { isValid: false, errors }; } }; } /** * Schema builder utilities for common patterns */ exports.yupSchemas = { email: () => { return Promise.resolve().then(() => __importStar(require('yup'))).then(yup => yup.string().email('Please enter a valid email address').required('Email is required')); }, password: (minLength = 8) => { return Promise.resolve().then(() => __importStar(require('yup'))).then(yup => yup.string() .min(minLength, `Password must be at least ${minLength} characters`) .matches(/[A-Z]/, 'Password must contain at least one uppercase letter') .matches(/[a-z]/, 'Password must contain at least one lowercase letter') .matches(/[0-9]/, 'Password must contain at least one number') .matches(/[^A-Za-z0-9]/, 'Password must contain at least one special character') .required('Password is required')); }, phone: () => { return Promise.resolve().then(() => __importStar(require('yup'))).then(yup => yup.string() .matches(/^\+?[1-9]\d{1,14}$/, 'Please enter a valid phone number') .required('Phone number is required')); }, url: () => { return Promise.resolve().then(() => __importStar(require('yup'))).then(yup => yup.string().url('Please enter a valid URL').required('URL is required')); }, required: (message = 'This field is required') => { return Promise.resolve().then(() => __importStar(require('yup'))).then(yup => yup.string().required(message)); }, number: (min, max) => { return Promise.resolve().then(() => __importStar(require('yup'))).then(yup => { let schema = yup.number().typeError('Must be a number'); if (min !== undefined) { schema = schema.min(min, `Must be at least ${min}`); } if (max !== undefined) { schema = schema.max(max, `Must be no more than ${max}`); } return schema.required('This field is required'); }); } }; /** * Form-level validation using Yup */ class YupFormValidator { constructor(schema) { this.fields = new Map(); this.schema = schema; } /** * Register a field with its schema key */ registerField(element, key) { this.fields.set(element, key); } /** * Validate a specific field */ async validateField(element) { const key = this.fields.get(element); if (!key) { return { isValid: true, errors: [] }; } const value = this.getElementValue(element); try { // Get field schema from object schema const fieldSchema = this.schema.fields[key]; if (!fieldSchema) { return { isValid: true, errors: [] }; } await fieldSchema.validate(value, { abortEarly: false }); return { isValid: true, errors: [] }; } catch (error) { const errors = []; if (error.inner && Array.isArray(error.inner)) { errors.push(...error.inner.map((err) => err.message)); } else if (error.message) { errors.push(error.message); } else { errors.push('Invalid value'); } return { isValid: false, errors }; } } /** * Validate entire form */ async validateForm() { const formData = {}; const errors = {}; // Collect all field values this.fields.forEach((key, element) => { formData[key] = this.getElementValue(element); }); try { const validatedData = await this.schema.validate(formData, { abortEarly: false }); return { isValid: true, errors: {}, data: validatedData }; } catch (error) { if (error.inner && Array.isArray(error.inner)) { error.inner.forEach((err) => { const path = err.path || 'unknown'; if (!errors[path]) { errors[path] = []; } errors[path].push(err.message); }); } else if (error.message) { errors.general = [error.message]; } return { isValid: false, errors }; } } getElementValue(element) { if (element instanceof HTMLInputElement) { if (element.type === 'checkbox') { return element.checked; } if (element.type === 'number') { return element.value ? Number(element.value) : undefined; } if (element.type === 'date') { return element.value ? new Date(element.value) : undefined; } return element.value; } if (element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) { return element.value; } return element.textContent || ''; } } exports.YupFormValidator = YupFormValidator; /** * Helper function to create form validator */ function createYupFormValidator(schema) { return new YupFormValidator(schema); } /** * Integration with neumorphic components */ exports.yupIntegration = { /** * Setup form validation with Yup schema */ async setupForm(formElement, schema, components) { const validator = new YupFormValidator(schema); // Register all components with their field names Object.entries(components).forEach(([key, component]) => { if (component && component.element) { validator.registerField(component.element, key); // Setup real-time validation component.element.addEventListener('blur', async () => { const result = await validator.validateField(component.element); if (!result.isValid && component.clearErrors && component.validate) { component.clearErrors(); // Create a validation function that returns the Yup result component.validate = () => result; component.updateValidationState?.(); } }); } }); // Setup form submission validation formElement.addEventListener('submit', async (e) => { e.preventDefault(); const result = await validator.validateForm(); if (result.isValid) { // Form is valid, can submit const event = new CustomEvent('np:form-valid', { detail: { data: result.data } }); formElement.dispatchEvent(event); } else { // Show field-specific errors Object.entries(result.errors).forEach(([fieldName, fieldErrors]) => { const component = components[fieldName]; if (component && component.clearErrors) { component.clearErrors(); // Set custom validation result component._validationResult = { isValid: false, errors: fieldErrors }; component.updateValidationState?.(); } }); const event = new CustomEvent('np:form-invalid', { detail: { errors: result.errors } }); formElement.dispatchEvent(event); } }); return validator; }, /** * Setup field-level validation with conditional schemas */ setupConditionalValidation(component, schemaBuilder, getFormData) { if (!component || !component.element) return; component.element.addEventListener('blur', async () => { try { const formData = getFormData(); const schema = schemaBuilder(formData); const fieldName = component.element.name || component.element.id; if (schema.fields && schema.fields[fieldName]) { const value = component.getValue ? component.getValue() : component.element.value; await schema.fields[fieldName].validate(value); if (component.clearErrors) { component.clearErrors(); } } } catch (error) { if (component.clearErrors && component.validate) { component.clearErrors(); component._validationResult = { isValid: false, errors: [error.message || 'Invalid value'] }; component.updateValidationState?.(); } } }); } }; /** * Advanced Yup utilities for neumorphic components */ exports.yupUtils = { /** * Create a cross-field validation schema */ createCrossFieldValidation: async (dependencies) => { const yup = await Promise.resolve().then(() => __importStar(require('yup'))); return yup.object().test('cross-field', 'Fields must match', function (value) { const { path, createError } = this; // Example: password confirmation if (dependencies.includes('password') && dependencies.includes('confirmPassword')) { if (value.password !== value.confirmPassword) { return createError({ path: 'confirmPassword', message: 'Passwords must match' }); } } return true; }); }, /** * Create async validation (e.g., for username availability) */ createAsyncValidation: (checkFunction, errorMessage = 'Value is not available') => { return Promise.resolve().then(() => __importStar(require('yup'))).then(yup => yup.string().test('async-validation', errorMessage, async function (value) { if (!value) return true; // Let required handle empty values try { const isValid = await checkFunction(value); return isValid; } catch { return false; } })); }, /** * Create conditional validation based on other fields */ createConditionalValidation: (condition, trueSchema, falseSchema) => { return Promise.resolve().then(() => __importStar(require('yup'))).then(yup => yup.mixed().when('$context', (context, schema) => { return condition(context) ? trueSchema : (falseSchema || schema); })); } }; // Example usage documentation exports.examples = { basicField: ` import * as yup from 'yup' import { yupAdapter } from 'neumorphic-peripheral/adapters/yup' const emailSchema = yup.string().email().required() np.input(emailElement, { validate: yupAdapter(emailSchema) }) `, asyncValidation: ` import { yupUtils } from 'neumorphic-peripheral/adapters/yup' const checkUsernameAvailability = async (username) => { const response = await fetch(\`/api/check-username/\${username}\`) return response.ok } const usernameSchema = await yupUtils.createAsyncValidation( checkUsernameAvailability, 'Username is already taken' ) np.input(usernameElement, { validate: yupAdapterAsync(usernameSchema) }) `, complexForm: ` import * as yup from 'yup' import { yupIntegration } from 'neumorphic-peripheral/adapters/yup' const formSchema = yup.object({ email: yup.string().email().required(), password: yup.string().min(8).required(), confirmPassword: yup.string() .oneOf([yup.ref('password')], 'Passwords must match') .required(), age: yup.number().min(18, 'Must be at least 18').required() }) const components = { email: np.input(emailEl), password: np.password(passwordEl), confirmPassword: np.password(confirmEl), age: np.input(ageEl) } const validator = await yupIntegration.setupForm( formElement, formSchema, components ) ` };