UNPKG

@tachui/primitives

Version:

Basic UI components for tachUI framework

391 lines (390 loc) 11.7 kB
import { withModifiers as y, createSignal as b, createEffect as u, h as p, text as g, ComponentWithCSSClasses as E, isSignal as h } from "@tachui/core"; var v = Object.defineProperty, S = (t, e, s) => e in t ? v(t, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : t[e] = s, o = (t, e, s) => S(t, typeof e != "symbol" ? e + "" : e, s); class F { constructor(e) { this.props = e, o(this, "type", "component"), o(this, "id"), o(this, "mounted", !1), o(this, "cleanup", []), o(this, "formElement", null), o(this, "validationErrors"), o(this, "setValidationErrors"), o(this, "setIsValid"), o(this, "handleSubmit", async (n) => { if (n.preventDefault(), this.props.validateOnSubmit !== !1) { const a = this.validateForm(); if (this.setValidationErrors(a), a.length > 0) return; } if (this.props.onSubmit) { const a = this.extractFormData(); await this.props.onSubmit(a); } }), o(this, "handleChange", () => { if (this.props.validateOnChange) { const n = this.validateForm(); this.setValidationErrors(n); } }), this.id = `form-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const [s, r] = b([]), [, i] = b(!0); this.validationErrors = s, this.setValidationErrors = r, this.setIsValid = i, u(() => { const n = this.validationErrors(), a = n.length === 0; this.setIsValid(a), this.props.onValidationChange && this.props.onValidationChange(a, n); }); } /** * Extract form data from form elements */ extractFormData() { if (!this.formElement) return {}; const e = new FormData(this.formElement), s = {}; for (const [r, i] of e.entries()) s[r] ? Array.isArray(s[r]) ? s[r].push(i) : s[r] = [s[r], i] : s[r] = i; return s; } /** * Validate all form fields */ validateForm() { if (!this.formElement) return []; const e = []; return this.formElement.querySelectorAll("input, select, textarea").forEach((r) => { const i = r, n = i.name || i.id; i.checkValidity() || e.push({ field: n, message: i.validationMessage, code: "html5_validation" }), i.hasAttribute("required") && !i.value.trim() && e.push({ field: n, message: `${n} is required`, code: "required" }); }), e; } /** * Get form styling based on style prop */ getFormStyles() { const { style: e = "automatic", spacing: s = 16 } = this.props, r = { display: "flex", flexDirection: "column", gap: `${s}px` }; switch (e) { case "grouped": return { ...r, backgroundColor: "#ffffff", border: "1px solid #e0e0e0", borderRadius: "12px", padding: "20px", boxShadow: "0 2px 8px rgba(0,0,0,0.1)" }; case "inset": return { ...r, backgroundColor: "#f8f9fa", border: "1px solid #e9ecef", borderRadius: "8px", padding: "16px", margin: "0 16px" }; case "plain": return r; default: return { ...r, backgroundColor: "#ffffff", borderRadius: "8px", padding: "16px" }; } } /** * Render validation summary */ renderValidationSummary() { const e = this.validationErrors(); return !this.props.showValidationSummary || e.length === 0 ? null : p( "div", { style: { backgroundColor: "#fff5f5", border: "1px solid #fed7d7", borderRadius: "6px", padding: "12px 16px", marginBottom: "16px" } }, p( "div", { style: { fontSize: "14px", fontWeight: "600", color: "#e53e3e", marginBottom: "8px" } }, g("Please fix the following errors:") ), p( "ul", { style: { margin: "0", paddingLeft: "20px", fontSize: "14px", color: "#c53030" } }, ...e.map( (s) => p("li", { key: s.field }, g(s.message)) ) ) ); } render() { const { children: e = [], accessibilityLabel: s, accessibilityRole: r = "form" } = this.props; return [ p( "form", { ref: (i) => { this.formElement = i, i && !this.mounted && (i.addEventListener("submit", this.handleSubmit), i.addEventListener("change", this.handleChange), i.addEventListener("input", this.handleChange), this.cleanup.push(() => { i.removeEventListener("submit", this.handleSubmit), i.removeEventListener("change", this.handleChange), i.removeEventListener("input", this.handleChange); }), this.mounted = !0); }, style: this.getFormStyles(), "aria-label": s, role: r, noValidate: !0 // We handle validation manually }, ...this.renderValidationSummary() !== null ? [this.renderValidationSummary()] : [], ...e.flatMap((i) => i.render()) ) ]; } } function d(t, e = {}) { const s = { ...e, children: t }, r = new F(s); return y(r); } const B = { /** * Automatic form styling (default) */ Automatic(t, e = {}) { return d(t, { ...e, style: "automatic" }); }, /** * Grouped form with container styling */ Grouped(t, e = {}) { return d(t, { ...e, style: "grouped" }); }, /** * Inset form styling */ Inset(t, e = {}) { return d(t, { ...e, style: "inset" }); }, /** * Plain form without styling */ Plain(t, e = {}) { return d(t, { ...e, style: "plain" }); } }, I = { /** * Common validation rules */ rules: { // biome-ignore lint/suspicious/noExplicitAny: Validation rules accept any input type required: (t) => t != null && t !== "", email: (t) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t), minLength: (t) => (e) => e.length >= t, maxLength: (t) => (e) => e.length <= t, pattern: (t) => (e) => t.test(e), number: (t) => !Number.isNaN(Number(t)), integer: (t) => Number.isInteger(Number(t)), min: (t) => (e) => Number(e) >= t, max: (t) => (e) => Number(e) <= t }, /** * Validate a single field */ validateField(t, e) { for (const s of e) { const r = s(t); if (r !== !0) return { field: "field", message: typeof r == "string" ? r : "Validation failed", code: "custom_validation" }; } return null; } }; var V = Object.defineProperty, T = (t, e, s) => e in t ? V(t, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : t[e] = s, l = (t, e, s) => T(t, typeof e != "symbol" ? e + "" : e, s); const c = { colors: { background: "#FFFFFF", border: "#D1D1D6", text: "#000000", placeholder: "#8E8E93", focusBorder: "#007AFF", disabledBackground: "#F2F2F7", disabledText: "#8E8E93" }, spacing: { padding: 8, borderWidth: 1 }, borderRadius: 4, fontSize: 16, fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', transition: "border-color 0.2s ease, background-color 0.2s ease" }; class C extends E { constructor(e) { super(), l(this, "type", "component"), l(this, "id"), l(this, "props"), l(this, "theme", c), l(this, "inputElement", null), l(this, "handleInput", (s) => { const i = s.target.value; this.setText(i), this.props.onChange && this.props.onChange(i); }), l(this, "handleKeyDown", (s) => { s.key === "Enter" && this.props.onSubmit && (s.preventDefault(), this.props.onSubmit(this.getText())); }), l(this, "handleFocus", () => { this.props.onFocus && this.props.onFocus(), this.inputElement && (this.inputElement.style.borderColor = this.theme.colors.focusBorder); }), l(this, "handleBlur", () => { this.props.onBlur && this.props.onBlur(), this.inputElement && (this.inputElement.style.borderColor = this.theme.colors.border); }), this.props = e, this.id = `basicinput-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } resolveValue(e) { return h(e) ? e() : e; } getText() { return this.resolveValue(this.props.text); } setText(e) { this.props.setText && this.props.setText(e); } getPlaceholder() { return this.resolveValue(this.props.placeholder || ""); } getInputType() { return this.resolveValue(this.props.inputType || "text"); } isDisabled() { return this.resolveValue(this.props.disabled || !1); } isReadonly() { return this.resolveValue(this.props.readonly || !1); } getInputStyles() { const e = this.isDisabled(); return { width: "100%", padding: `${this.theme.spacing.padding}px`, border: `${this.theme.spacing.borderWidth}px solid ${this.theme.colors.border}`, borderRadius: `${this.theme.borderRadius}px`, backgroundColor: e ? this.theme.colors.disabledBackground : this.theme.colors.background, color: e ? this.theme.colors.disabledText : this.theme.colors.text, fontSize: `${this.theme.fontSize}px`, fontFamily: this.theme.fontFamily, outline: "none", transition: this.theme.transition, cursor: e ? "not-allowed" : "text" }; } render() { const e = this.getText(), s = this.getPlaceholder(), r = this.getInputType(), i = this.isDisabled(), n = this.isReadonly(), a = ["tachui-basic-input"], x = this.createClassString(this.props, a), m = p("input", { id: this.id, className: x, type: r, value: e, placeholder: s, disabled: i, readonly: n, "aria-label": this.props.accessibilityLabel, "aria-describedby": this.props.accessibilityHint ? `${this.id}-hint` : void 0, style: this.getInputStyles(), oninput: this.handleInput, onkeydown: this.handleKeyDown, onfocus: this.handleFocus, onblur: this.handleBlur }); return this.inputElement = m.element, u(() => { if (this.inputElement && h(this.props.text)) { const f = this.getText(); this.inputElement.value !== f && (this.inputElement.value = f); } }), u(() => { this.inputElement && (h(this.props.placeholder) || h(this.props.inputType) || h(this.props.disabled)) && (h(this.props.placeholder) && (this.inputElement.placeholder = this.getPlaceholder()), h(this.props.inputType) && (this.inputElement.type = this.getInputType()), h(this.props.disabled) && (this.inputElement.disabled = this.isDisabled())); }), [m]; } } function D(t) { return y(new C(t)); } const $ = { /** * Create a search input */ search(t, e, s) { return { text: t, setText: e, inputType: "search", placeholder: "Search...", onSubmit: s }; }, /** * Create an email input */ email(t, e) { return { text: t, setText: e, inputType: "email", placeholder: "Enter email address" }; }, /** * Create a password input */ password(t, e) { return { text: t, setText: e, inputType: "password", placeholder: "Enter password" }; }, /** * Create a phone number input */ phone(t, e) { return { text: t, setText: e, inputType: "tel", placeholder: "Enter phone number" }; } }, _ = { theme: c, /** * Create a custom theme */ createTheme(t) { return { ...c, ...t }; } }; export { d as B, F as a, B as b, I as c, D as d, _ as e, $ as f };