@tachui/primitives
Version:
Basic UI components for tachUI framework
391 lines (390 loc) • 11.7 kB
JavaScript
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
};