@tachui/primitives
Version:
Basic UI components for tachUI framework
1,652 lines (1,651 loc) • 50.3 kB
JavaScript
import { withModifiers as T, ComponentWithCSSClasses as K, createSignal as z, useLifecycle as X, createEffect as W, isSignal as l, ColorAsset as A, h as a, text as p, ConcatenatedComponent as R, createComputed as Z } from "@tachui/core";
import { T as V } from "./Text-DYyFBJ6i.js";
var q = Object.defineProperty, J = (s, e, t) => e in s ? q(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t, v = (s, e, t) => J(s, typeof e != "symbol" ? e + "" : e, t);
const Q = {
colors: {
primary: "#007AFF",
secondary: "#5856D6",
destructive: "#FF3B30",
background: "#F2F2F7",
surface: "#FFFFFF",
onPrimary: "#FFFFFF",
onSecondary: "#FFFFFF",
onSurface: "#000000",
border: "#C7C7CC",
disabled: "#8E8E93"
},
spacing: {
small: 8,
medium: 12,
large: 16
},
borderRadius: {
small: 6,
medium: 8,
large: 12
},
typography: {
small: { size: 14, weight: "500" },
medium: { size: 16, weight: "500" },
large: { size: 18, weight: "600" }
}
};
class ee extends K {
constructor(e, t = Q) {
super(), this.props = e, v(this, "type", "component"), v(this, "id"), v(this, "mounted", !1), v(this, "cleanup", []), v(this, "stateSignal"), v(this, "setState"), v(this, "theme"), this.id = `button-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, this.theme = t;
const [i, r] = z("normal");
this.stateSignal = i, this.setState = r, this.setupDOMEventListeners();
}
/**
* Set up DOM event listeners for button interactions
*/
setupDOMEventListeners() {
X(this, {
onDOMReady: (e, t) => {
t instanceof HTMLButtonElement && (this.attachInteractionEvents(t), this.setupReactiveStyles(t));
}
});
}
/**
* Set up reactive style updates based on state changes
*/
setupReactiveStyles(e) {
const t = W(() => {
this.stateSignal(), this.isEnabled(), this.isLoading();
const { tint: i, backgroundColor: r, foregroundColor: n } = this.props;
i && l(i) ? i() : i instanceof A && i.resolve(), r && l(r) ? r() : r instanceof A && r.resolve(), n && l(n) ? n() : n instanceof A && n.resolve();
const o = this.getButtonStyles();
this.applyStylesToElement(e, o);
});
this.cleanup.push(() => {
t && typeof t.dispose == "function" && t.dispose();
});
}
/**
* Apply computed styles to the button element, respecting modifier-applied styles
*/
applyStylesToElement(e, t) {
Object.entries(t).forEach(([i, r]) => {
const n = this.camelToKebabCase(i);
if (typeof r == "string" || typeof r == "number") {
const o = e.style.getPropertyValue(n);
n === "transform" ? e.style.setProperty(n, String(r)) : o && o !== "" && o !== "inherit" || e.style.setProperty(n, String(r));
}
});
}
/**
* Convert camelCase to kebab-case for CSS properties
*/
camelToKebabCase(e) {
return e.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
}
/**
* Resolve color value from string, signal, or asset
*/
resolveColorValue(e) {
if (e) {
if (typeof e == "string")
return e;
if (l(e))
return e();
if (e instanceof A)
return e.resolve();
}
}
/**
* Attach interaction event listeners to the button element
*/
attachInteractionEvents(e) {
const t = () => {
const c = this.isEnabled();
return typeof c == "boolean" ? c : c();
};
let i = null;
const r = (c) => {
t() && c.button === 0 && (this.setState("pressed"), i = () => {
this.stateSignal() === "pressed" && this.setState("normal"), i && (document.removeEventListener("mouseup", i), i = null);
}, document.addEventListener("mouseup", i));
}, n = () => {
t() && this.stateSignal() === "pressed" && (this.setState("normal"), i && (document.removeEventListener("mouseup", i), i = null));
}, o = () => {
t() && (this.setState("normal"), i && (document.removeEventListener("mouseup", i), i = null));
}, d = () => {
t() && this.stateSignal() !== "pressed" && this.setState("focused");
}, u = () => {
t() && this.stateSignal() === "focused" && this.setState("normal");
}, y = (c) => {
t() && (c.key === " " || c.key === "Enter") && (c.preventDefault(), this.setState("pressed"));
}, f = (c) => {
t() && (c.key === " " || c.key === "Enter") && (c.preventDefault(), this.setState("normal"), this.props.action?.());
};
e.addEventListener("mousedown", r), e.addEventListener("mouseup", n), e.addEventListener("mouseleave", o), e.addEventListener("focus", d), e.addEventListener("blur", u), e.addEventListener("keydown", y), e.addEventListener("keyup", f), this.cleanup.push(() => {
e.removeEventListener("mousedown", r), e.removeEventListener("mouseup", n), e.removeEventListener("mouseleave", o), e.removeEventListener("focus", d), e.removeEventListener("blur", u), e.removeEventListener("keydown", y), e.removeEventListener("keyup", f), i && (document.removeEventListener("mouseup", i), i = null);
});
}
/**
* Check if button is enabled
*/
isEnabled() {
const { isEnabled: e } = this.props;
return e === void 0 ? !0 : typeof e == "boolean" || l(e) ? e : !0;
}
/**
* Render the button component
*/
render() {
const e = this.isEnabled(), t = [];
this.props.systemImage && t.push(
a(
"span",
{
class: "button-icon",
style: {
marginRight: "8px",
fontSize: "1.2em"
}
},
p(this.props.systemImage)
)
), t.push(
a(
"span",
{
class: "button-title"
},
p(this.props.title || "")
)
);
const i = ["tachui-button"];
return [{
type: "element",
tag: "button",
props: {
className: this.createClassString(this.props, i),
type: "button",
disabled: typeof e == "boolean" ? !e : () => !e(),
// Invert enabled to disabled
onClick: this.props.action ? () => {
try {
this.props.action?.();
} catch (o) {
console.error("Button action error:", o);
}
} : void 0,
// Pass through debug label for debug system
...this.props.debugLabel && { debugLabel: this.props.debugLabel }
},
children: t,
// Attach component metadata for modifier processing
componentMetadata: {
id: this.id,
type: "Button"
// Note: modifiers are attached automatically by the modifier system
}
}];
}
/**
* Check if button is in loading state
*/
isLoading() {
const { isLoading: e } = this.props;
return e === void 0 ? !1 : typeof e == "boolean" ? e : l(e) ? e() : !1;
}
/**
* Check if the button has color-related modifiers applied
*/
hasColorModifiers() {
let e = this.modifiers;
return !e && this.modifierBuilder && (e = this.modifierBuilder.modifiers), !e && this.modifiableComponent && (e = this.modifiableComponent.modifiers), !e || !Array.isArray(e) ? !1 : e.some((t) => t.type === "appearance" || t.constructor?.name === "AppearanceModifier" ? t.properties && (t.properties.foregroundColor !== void 0 || t.properties.color !== void 0) : t.properties ? ["foregroundColor", "color", "textColor"].some((r) => t.properties[r] !== void 0) : !1);
}
/**
* Check if the button has typography-related modifiers applied
*/
hasTypographyModifiers() {
let e = this.modifiers;
return !e && this.modifierBuilder && (e = this.modifierBuilder.modifiers), !e && this.modifiableComponent && (e = this.modifiableComponent.modifiers), !e || !Array.isArray(e) ? !1 : e.some((i) => i.type === "typography" || i.constructor?.name === "TypographyModifier" ? i.properties && (i.properties.transform !== void 0 || i.properties.textTransform !== void 0) : !1);
}
/**
* Get computed button styles based on variant, size, role, and state
*/
// biome-ignore lint/suspicious/noExplicitAny: CSS styles require flexible property types
getButtonStyles() {
const {
variant: e,
size: t,
role: i = "none",
tint: r,
backgroundColor: n,
foregroundColor: o
} = this.props, d = this.stateSignal(), u = this.isLoading(), y = this.isEnabled(), f = this.hasColorModifiers(), c = this.hasTypographyModifiers(), C = {
// Only set color property if no modifiers will handle it
// This prevents conflicts between Button styles and AppearanceModifier
...!f && !o && !e && { color: "inherit" },
// Only set text-related properties if no typography modifiers will handle them
// This prevents conflicts between Button styles and TypographyModifier
...!c && {
fontStyle: "inherit",
lineHeight: "inherit",
textTransform: "inherit",
textDecoration: "inherit",
textIndent: "inherit",
textShadow: "inherit",
wordSpacing: "inherit",
textOrientation: "inherit",
writingMode: "inherit",
direction: "inherit"
}
// Let modifiers control: fontFamily, fontSize, fontWeight, letterSpacing, textAlign
};
if (t) {
const k = this.theme.spacing[t] || this.theme.spacing.medium, j = this.theme.borderRadius[t] || this.theme.borderRadius.medium, H = this.theme.typography[t] || this.theme.typography.medium, Y = {
small: "32px",
medium: "40px",
large: "48px"
};
C.padding = `${k}px ${k * 2}px`, C.borderRadius = `${j}px`, C.fontSize = `${H.size}px`, C.fontWeight = H.weight, C.minHeight = Y[t];
}
let g = "transparent", S = "transparent", _ = "1px", L;
const G = this.resolveColorValue(r), D = this.resolveColorValue(n), O = this.resolveColorValue(o);
if (e) {
const k = G || this.theme.colors.primary;
switch (e) {
case "filled":
i === "destructive" ? g = this.theme.colors.destructive : i === "cancel" ? g = this.theme.colors.secondary : g = k, L = this.theme.colors.onPrimary;
break;
case "outlined":
S = i === "destructive" ? this.theme.colors.destructive : k, L = i === "destructive" ? this.theme.colors.destructive : k;
break;
case "bordered":
g = this.theme.colors.background, S = this.theme.colors.border;
break;
case "borderedProminent":
g = this.theme.colors.surface, S = this.theme.colors.primary, _ = "2px";
break;
}
}
D && (g = D), O && (L = O);
let F = "1", P = "auto", M, B = "none";
y ? u ? (F = "0.6", P = "none") : d === "pressed" ? (g = this.darkenColor(g, 0.1), S = this.darkenColor(S, 0.1), M = "scale(0.95)") : d === "focused" && (B = "0 0 0 3px #007AFF40") : (g = this.theme.colors.disabled, S = this.theme.colors.disabled, L = this.theme.colors.disabled, F = "0.6", P = "none");
const h = {
...C
// Size-based styles (only if size provided)
};
return e && (h.backgroundColor = g, h.borderColor = S, h.borderWidth = _, L !== void 0 && (h.color = L)), D && (h.backgroundColor = D), O && (h.color = O), h.cursor = y ? "pointer" : "not-allowed", h.opacity = F, h.pointerEvents = P, h.transform = M !== void 0 ? M : "none", h.boxShadow = B, h.transition = "all 0.2s ease", h;
}
/**
* Handle button press with proper state management
*/
async handlePress() {
if (!(!this.isEnabled() || this.isLoading()) && (this.triggerHapticFeedback(), this.setState("pressed"), setTimeout(() => {
this.isEnabled() && this.setState("normal");
}, 150), this.props.action))
try {
const e = this.props.action();
e && typeof e.then == "function" && await e;
} catch (e) {
console.error("Button action failed:", e);
}
}
/**
* Trigger haptic feedback (mobile Safari support)
*/
triggerHapticFeedback() {
if (this.props.hapticFeedback !== !1 && typeof window < "u" && "navigator" in window) {
const e = window.navigator;
e.vibrate && e.vibrate(10);
}
}
/**
* Helper to darken a color for pressed states
*/
darkenColor(e, t) {
if (e === "transparent") return e;
if (e.startsWith("#")) {
const i = e.slice(1), r = parseInt(i, 16), n = Math.max(0, Math.floor((r >> 16) * (1 - t))), o = Math.max(0, Math.floor((r >> 8 & 255) * (1 - t))), d = Math.max(0, Math.floor((r & 255) * (1 - t)));
return `#${(n << 16 | o << 8 | d).toString(16).padStart(6, "0")}`;
}
return e;
}
// ============================================================================
// Concatenation Support (Phase 3.1)
// ============================================================================
/**
* Concatenate this button with another concatenatable component
*/
concat(e) {
const t = this.toSegment(), i = e.toSegment(), r = {
totalSegments: e instanceof R ? e.segments.length + 1 : 2,
accessibilityRole: e instanceof R ? this.mergeAccessibilityRoles(
"composite",
e.metadata.accessibilityRole
) : this.determineAccessibilityRole(e),
semanticStructure: "inline"
// Buttons are typically inline in concatenation
};
return new R([t, i], r);
}
/**
* Convert this button to a segment for concatenation
*/
toSegment() {
return {
id: this.id,
component: this,
modifiers: [],
// Buttons don't typically have concatenation-specific modifiers
render: () => {
const e = this.render();
return Array.isArray(e) ? e[0] : e;
}
};
}
/**
* Check if this component supports concatenation
*/
isConcatenatable() {
return !0;
}
/**
* Determine accessibility role for concatenation
*/
determineAccessibilityRole(e) {
switch (e.constructor.name) {
case "EnhancedText":
return "composite";
// Button + Text = composite (interactive content)
case "EnhancedImage":
return "composite";
// Button + Image = composite (interactive content)
case "EnhancedButton":
case "EnhancedLink":
return "composite";
// Button + Interactive = composite
default:
return "composite";
}
}
/**
* Merge accessibility roles when combining components
*/
mergeAccessibilityRoles(e, t) {
return "composite";
}
}
function E(s, e, t = {}) {
const i = {
...t,
title: s,
...e && { action: e }
}, r = new ee(i);
return T(r);
}
const he = {
/**
* Filled button (primary)
*/
Filled: (s, e, t = {}) => E(s, e, { ...t, variant: "filled" }),
/**
* Outlined button
*/
Outlined: (s, e, t = {}) => E(s, e, { ...t, variant: "outlined" }),
/**
* Plain button (text only)
*/
Plain: (s, e, t = {}) => E(s, e, { ...t, variant: "plain" }),
/**
* Bordered button
*/
Bordered: (s, e, t = {}) => E(s, e, { ...t, variant: "bordered" }),
/**
* Destructive button
*/
Destructive: (s, e, t = {}) => E(s, e, { ...t, role: "destructive" }),
/**
* Cancel button
*/
Cancel: (s, e, t = {}) => E(s, e, { ...t, role: "cancel" })
}, te = `
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
if (typeof document < "u") {
const s = document.createElement("style");
s.textContent = te, document.head.appendChild(s);
}
var ie = Object.defineProperty, se = (s, e, t) => e in s ? ie(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t, I = (s, e, t) => se(s, typeof e != "symbol" ? e + "" : e, t);
class U {
constructor(e) {
I(this, "type", "component"), I(this, "id"), I(this, "props"), this.props = e, this.id = `enhanced-link-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
resolveValue(e) {
return l(e) ? e() : e;
}
getDestination() {
const e = this.resolveValue(this.props.destination);
return e instanceof URL ? e.toString() : e;
}
getTarget() {
const e = this.resolveValue(this.props.target);
if (e)
return e;
const t = this.resolveValue(this.props.destination), i = t instanceof URL ? t.toString() : t;
return this.isExternalURL(i) ? "_blank" : "_self";
}
getRoutingMode() {
return this.resolveValue(this.props.routingMode) || "auto";
}
getRel() {
const e = this.resolveValue(this.props.rel), t = this.getTarget(), i = this.getDestination(), n = this.isExternalURL(i) && t === "_blank" ? "noopener noreferrer" : "";
return e && n ? `${e} ${n}`.trim() : e || n;
}
getDownload() {
return this.resolveValue(this.props.download);
}
isDisabled() {
return this.resolveValue(this.props.disabled) || !1;
}
getAccessibilityLabel() {
return this.resolveValue(this.props.accessibilityLabel);
}
getAccessibilityHint() {
return this.resolveValue(this.props.accessibilityHint);
}
getAccessibilityRole() {
return this.resolveValue(this.props.accessibilityRole);
}
shouldUseInternalRouting(e) {
const t = this.getRoutingMode();
return t === "internal" ? !0 : t === "external" ? !1 : t === "spa" ? !0 : !(this.isSpecialScheme(e) || this.isExternalURL(e));
}
isSpecialScheme(e) {
return /^(mailto|tel|sms|ftp|file):/.test(e);
}
async handleClick(e) {
if (this.isDisabled()) {
e.preventDefault();
return;
}
if (this.props.onPress)
try {
this.props.onPress(e);
} catch (n) {
console.error("Link onPress error:", n);
}
const t = this.getDestination();
if (this.isSpecialScheme(t) || (e.preventDefault(), this.props.openURLAction && this.props.openURLAction(t) === "handled"))
return;
if (this.props.onBeforeNavigation)
try {
if (!await this.props.onBeforeNavigation(t))
return;
} catch (n) {
throw console.error("Link onBeforeNavigation error:", n), n;
}
const i = this.getTarget(), r = this.getRoutingMode();
if (this.shouldUseInternalRouting(t)) {
if (this.props.onInternalNavigation)
try {
if (await this.props.onInternalNavigation(t))
return;
} catch (n) {
console.error("Link onInternalNavigation error:", n);
}
r === "spa" ? this.handleSPANavigation(t) : i === "_blank" ? window.open(t, i) : window.location.href = t;
} else
i === "_blank" || i === "_top" || i === "_parent" ? window.open(t, i) : window.location.href = t;
}
handleSPANavigation(e) {
typeof window < "u" && window.history && window.history.pushState ? (window.history.pushState(null, "", e), window.dispatchEvent(new PopStateEvent("popstate"))) : window.location.href = e;
}
isExternalURL(e) {
try {
return new URL(e, window.location.href).hostname !== window.location.hostname;
} catch {
return !1;
}
}
renderContent() {
if (this.props.label) {
const n = this.props.label().render();
return Array.isArray(n) ? n : [n];
}
if (this.props.children)
return this.renderChildren();
let e;
this.props.text ? e = this.props.text : l(this.props.destination) ? e = Z(() => this.getDestination()) : e = this.getDestination();
const i = V(e).render();
return Array.isArray(i) ? i : [i];
}
renderChildren() {
const { children: e } = this.props;
return e ? Array.isArray(e) ? e.map((t) => this.renderChild(t)).flat() : this.renderChild(e) : [];
}
renderChild(e) {
if (typeof e == "string") {
const i = V(e).render();
return Array.isArray(i) ? i : [i];
} else {
const t = e.render();
return Array.isArray(t) ? t : [t];
}
}
render() {
const e = this.renderContent(), t = this.isDisabled(), i = {
id: this.id,
"data-component": "enhanced-link"
};
l(this.props.destination) ? i.href = () => this.getDestination() : i.href = this.getDestination(), this.props.target && (l(this.props.target) ? i.target = () => this.getTarget() : i.target = this.getTarget());
const r = this.getRel();
r && (l(this.props.rel) || l(this.props.target) ? i.rel = () => this.getRel() : i.rel = r);
const n = this.getDownload();
n !== void 0 && (l(this.props.download) ? i.download = () => this.getDownload() : n === !0 ? i.download = !0 : typeof n == "string" && (i.download = n));
const o = this.getAccessibilityLabel();
o && (l(this.props.accessibilityLabel) ? i["aria-label"] = () => this.getAccessibilityLabel() : i["aria-label"] = o);
const d = this.getAccessibilityHint();
d && (l(this.props.accessibilityHint) ? i["aria-describedby"] = () => this.getAccessibilityHint() : i["aria-describedby"] = d);
const u = this.getAccessibilityRole();
return u && (l(this.props.accessibilityRole) ? i.role = () => this.getAccessibilityRole() : i.role = u), t && (i["aria-disabled"] = "true", i.tabindex = "-1"), i.onclick = (f) => this.handleClick(f), i.style = {
textDecoration: "none",
color: "inherit",
cursor: t ? "not-allowed" : "pointer",
opacity: t ? 0.6 : 1
}, a("a", i, ...e);
}
// Concatenation support
concat(e) {
const t = this.toSegment(), i = e.toSegment(), r = {
totalSegments: 2,
accessibilityRole: "composite",
semanticStructure: "inline"
};
return new R([t, i], r);
}
toSegment() {
return {
id: this.id,
component: this,
modifiers: [],
render: () => this.render()
};
}
isConcatenatable() {
return !0;
}
}
function ue(s, e) {
if (e !== void 0) {
const r = {
text: s,
destination: e
}, n = new U(r);
return T(n);
}
const t = s, i = new U(t);
return T(i);
}
const x = {
/**
* Create an external link that opens in a new tab
*/
external(s, e, t = {}) {
return {
destination: s,
children: e,
target: "_blank",
routingMode: "external",
rel: "noopener noreferrer external",
accessibilityHint: "Opens in a new tab",
...t
};
},
/**
* Create an internal link for same-domain navigation
*/
internal(s, e, t = {}) {
return {
destination: s,
children: e,
routingMode: "internal",
target: "_self",
...t
};
},
/**
* Create a SPA link using History API
*/
spa(s, e, t = {}) {
return {
destination: s,
children: e,
routingMode: "spa",
target: "_self",
accessibilityHint: "Navigates within the app",
...t
};
},
/**
* Create an email link with optional subject and body
*/
email(s, e, t, i) {
let r = `mailto:${s}`;
const n = new URLSearchParams();
return e && n.append("subject", e), t && n.append("body", t), n.toString() && (r += `?${n.toString()}`), {
destination: r,
children: i || s,
accessibilityLabel: `Send email to ${s}`,
accessibilityHint: "Opens your email app"
};
},
/**
* Create a phone link with formatted display
*/
phone(s, e) {
return {
destination: `tel:${s.replace(/[^\d+]/g, "")}`,
children: e || s,
accessibilityLabel: `Call ${s}`,
accessibilityHint: "Opens your phone app"
};
},
/**
* Create a download link
*/
download(s, e, t) {
return {
destination: s,
children: t || `Download ${e || "file"}`,
download: e || !0,
accessibilityLabel: `Download ${e || "file"}`,
accessibilityHint: "Downloads file to your device"
};
},
/**
* Social media link utilities
*/
social: {
twitter(s, e) {
return x.external(
`https://twitter.com/${s}`,
e || `@${s}`,
{ accessibilityLabel: `Visit ${s} on Twitter` }
);
},
github(s, e) {
return x.external(
`https://github.com/${s}`,
e || s,
{ accessibilityLabel: `Visit ${s} on GitHub` }
);
},
linkedin(s, e) {
return x.external(
`https://linkedin.com/in/${s}`,
e || s,
{ accessibilityLabel: `Visit ${s} on LinkedIn` }
);
},
instagram(s, e) {
return x.external(
`https://instagram.com/${s}`,
e || `@${s}`,
{ accessibilityLabel: `Visit ${s} on Instagram` }
);
},
facebook(s, e) {
return x.external(
`https://facebook.com/${s}`,
e || s,
{ accessibilityLabel: `Visit ${s} on Facebook` }
);
}
},
/**
* App store link utilities
*/
appStore: {
ios(s, e) {
return x.external(
`https://apps.apple.com/app/id${s}`,
e || "Download from App Store",
{
accessibilityLabel: "Download from the iOS App Store",
accessibilityHint: "Opens App Store app or website"
}
);
},
android(s, e) {
return x.external(
`https://play.google.com/store/apps/details?id=${s}`,
e || "Get it on Google Play",
{
accessibilityLabel: "Download from Google Play Store",
accessibilityHint: "Opens Google Play Store app or website"
}
);
}
},
/**
* Create a custom OpenURL action
*/
openURLAction(s) {
return (e) => s(e) ? "handled" : "systemAction";
}
};
var ne = Object.defineProperty, re = (s, e, t) => e in s ? ne(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t, w = (s, e, t) => re(s, typeof e != "symbol" ? e + "" : e, t);
class oe {
constructor(e) {
this.props = e, w(this, "type", "component"), w(this, "id"), w(this, "mounted", !1), w(this, "cleanup", []), w(this, "toggleElement", null), w(this, "setIsAnimating"), w(this, "handleToggle", async (i) => {
if (i.preventDefault(), this.isDisabled()) return;
const r = !this.getIsOn();
this.props.animated !== !1 && (this.setIsAnimating(!0), setTimeout(() => {
this.setIsAnimating(!1);
}, 200)), this.props.onToggle && this.props.onToggle(r);
}), this.id = `toggle-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const [, t] = z(!1);
this.setIsAnimating = t, W(() => {
const i = this.getIsOn();
this.toggleElement && this.toggleElement.checked !== i && (this.toggleElement.checked = i);
});
}
/**
* Get current toggle state
*/
getIsOn() {
const { isOn: e } = this.props;
return l(e) ? e() : e;
}
// /**
// * Get toggle state as signal or static value for DOM renderer
// */
// private getIsOnForDOM(): boolean | (() => boolean) {
// const { isOn } = this.props
// if (isSignal(isOn)) {
// return isOn as () => boolean
// }
// return isOn as boolean
// }
/**
* Check if toggle is disabled
*/
isDisabled() {
const { disabled: e } = this.props;
return typeof e == "boolean" ? e : l(e) ? e() : !1;
}
// /**
// * Get disabled state as signal or static value for DOM renderer
// */
// private getDisabledForDOM(): boolean | (() => boolean) {
// const { disabled } = this.props
// if (typeof disabled === 'boolean') return disabled
// if (isSignal(disabled)) return disabled as () => boolean
// return false
// }
/**
* Resolve label content
*/
resolveLabel() {
const { label: e } = this.props;
return e ? typeof e == "string" ? e : typeof e == "function" || l(e) ? e() : e : null;
}
/**
* Helper to render component content safely
*/
renderComponentContent(e) {
const t = e.render();
return Array.isArray(t) ? t : [t];
}
/**
* Get toggle size styles
*/
getSizeStyles() {
const { size: e = "medium" } = this.props;
switch (e) {
case "small":
return {
width: "36px",
height: "20px",
thumbSize: "16px",
fontSize: "14px"
};
case "large":
return {
width: "56px",
height: "32px",
thumbSize: "28px",
fontSize: "18px"
};
default:
return {
width: "46px",
height: "26px",
thumbSize: "22px",
fontSize: "16px"
};
}
}
/**
* Render switch variant
*/
renderSwitch() {
const e = this.getIsOn(), t = this.isDisabled(), i = this.getSizeStyles(), {
color: r = "#007AFF",
offColor: n = "#e2e8f0",
thumbColor: o = "#ffffff",
animated: d = !0
} = this.props, u = e ? r : n, y = e ? `translateX(${parseInt(i.width) - parseInt(i.thumbSize) - 2}px)` : "translateX(2px)";
return a(
"div",
{
style: {
position: "relative",
display: "inline-block"
}
},
// Hidden input for form integration
a("input", {
ref: (f) => {
this.toggleElement = f, f && !this.mounted && (f.addEventListener("change", this.handleToggle), this.cleanup.push(() => {
f.removeEventListener("change", this.handleToggle);
}));
},
type: "checkbox",
checked: e,
disabled: t,
style: {
position: "absolute",
opacity: 0,
width: "100%",
height: "100%",
margin: 0,
cursor: t ? "not-allowed" : "pointer"
},
"aria-label": this.props.accessibilityLabel,
"aria-describedby": this.props.accessibilityHint ? `${this.id}-hint` : void 0
}),
// Switch track
a(
"div",
{
style: {
width: i.width,
height: i.height,
backgroundColor: u,
borderRadius: i.height,
position: "relative",
cursor: t ? "not-allowed" : "pointer",
opacity: t ? 0.5 : 1,
transition: d ? "background-color 0.2s ease" : "none",
border: "1px solid rgba(0,0,0,0.1)",
boxShadow: "inset 0 1px 2px rgba(0,0,0,0.1)"
},
onClick: this.handleToggle
},
// Switch thumb
a("div", {
style: {
width: i.thumbSize,
height: i.thumbSize,
backgroundColor: o,
borderRadius: "50%",
position: "absolute",
top: "50%",
transform: `translateY(-50%) ${y}`,
transition: d ? "transform 0.2s ease" : "none",
boxShadow: "0 2px 4px rgba(0,0,0,0.2)",
border: "1px solid rgba(0,0,0,0.1)"
}
})
)
);
}
/**
* Render checkbox variant
*/
renderCheckbox() {
const e = this.getIsOn(), t = this.isDisabled(), i = this.getSizeStyles(), { color: r = "#007AFF" } = this.props;
return a(
"div",
{
style: {
position: "relative",
display: "inline-block"
}
},
// Hidden input
a("input", {
ref: (n) => {
this.toggleElement = n, n && !this.mounted && (n.addEventListener("change", this.handleToggle), this.cleanup.push(() => {
n.removeEventListener("change", this.handleToggle);
}));
},
type: "checkbox",
checked: e,
disabled: t,
style: {
position: "absolute",
opacity: 0,
width: "100%",
height: "100%",
margin: 0,
cursor: t ? "not-allowed" : "pointer"
}
}),
// Checkbox visual
a(
"div",
{
style: {
width: i.thumbSize,
height: i.thumbSize,
border: `2px solid ${e ? r : "#d1d5db"}`,
borderRadius: "4px",
backgroundColor: e ? r : "#ffffff",
display: "flex",
alignItems: "center",
justifyContent: "center",
cursor: t ? "not-allowed" : "pointer",
opacity: t ? 0.5 : 1,
transition: "all 0.2s ease"
},
onClick: this.handleToggle
},
...e ? [
// Checkmark
a("div", {
style: {
width: "6px",
height: "10px",
border: "2px solid white",
borderTop: "none",
borderLeft: "none",
transform: "rotate(45deg) translateY(-1px)",
opacity: e ? 1 : 0,
transition: "opacity 0.1s ease"
}
})
] : []
)
);
}
/**
* Render button variant
*/
renderButton() {
const e = this.getIsOn(), t = this.isDisabled(), i = this.getSizeStyles(), { color: r = "#007AFF", offColor: n = "#f3f4f6" } = this.props;
return a(
"button",
{
ref: (o) => {
o && !this.mounted && (o.addEventListener("click", this.handleToggle), this.cleanup.push(() => {
o.removeEventListener("click", this.handleToggle);
}));
},
type: "button",
disabled: t,
style: {
padding: "8px 16px",
backgroundColor: e ? r : n,
color: e ? "#ffffff" : "#374151",
border: `1px solid ${e ? r : "#d1d5db"}`,
borderRadius: "6px",
fontSize: i.fontSize,
fontWeight: "500",
cursor: t ? "not-allowed" : "pointer",
opacity: t ? 0.5 : 1,
transition: "all 0.2s ease",
outline: "none"
},
"aria-pressed": e,
"aria-label": this.props.accessibilityLabel
},
p(e ? "ON" : "OFF")
);
}
/**
* Render toggle control based on variant
*/
renderToggleControl() {
const { variant: e = "switch" } = this.props;
switch (e) {
case "checkbox":
return this.renderCheckbox();
case "button":
return this.renderButton();
default:
return this.renderSwitch();
}
}
/**
* Render label content
*/
renderLabel() {
const e = this.resolveLabel();
if (!e) return null;
const t = this.getSizeStyles();
return a(
"span",
{
style: {
fontSize: t.fontSize,
color: this.isDisabled() ? "#9ca3af" : "#374151",
cursor: this.isDisabled() ? "not-allowed" : "pointer"
},
onClick: this.isDisabled() ? void 0 : this.handleToggle
},
...typeof e == "string" ? [p(e)] : this.renderComponentContent(e)
);
}
render() {
const {
labelPosition: e = "trailing",
spacing: t = 8,
accessibilityHint: i
} = this.props, r = this.renderLabel(), n = this.renderToggleControl(), o = e === "leading" && r ? [r, n] : r ? [n, r] : [n];
return a(
"div",
{
style: {
display: "flex",
alignItems: "center",
gap: r ? `${t}px` : "0",
cursor: this.isDisabled() ? "not-allowed" : "pointer"
},
"aria-describedby": i ? `${this.id}-hint` : void 0
},
...o,
...i ? [
a(
"div",
{
id: `${this.id}-hint`,
style: {
fontSize: "12px",
color: "#6b7280",
marginTop: "4px",
display: "block",
width: "100%"
}
},
p(i)
)
] : []
);
}
}
function m(s, e = {}) {
const t = { ...e, isOn: s }, i = new oe(t);
return T(i);
}
function N(s, e, t = {}) {
return m(e, { ...t, label: s });
}
const fe = {
/**
* Switch toggle (default)
*/
Switch(s, e = {}) {
return m(s, { ...e, variant: "switch" });
},
/**
* Checkbox toggle
*/
Checkbox(s, e = {}) {
return m(s, { ...e, variant: "checkbox" });
},
/**
* Button toggle
*/
Button(s, e = {}) {
return m(s, { ...e, variant: "button" });
},
/**
* Small toggle
*/
Small(s, e = {}) {
return m(s, { ...e, size: "small" });
},
/**
* Large toggle
*/
Large(s, e = {}) {
return m(s, { ...e, size: "large" });
},
/**
* Custom color toggle
*/
CustomColor(s, e, t = {}) {
return m(s, { ...t, color: e });
},
/**
* Toggle with leading label
*/
WithLeadingLabel(s, e, t = {}) {
return m(e, { ...t, label: s, labelPosition: "leading" });
},
/**
* Toggle with trailing label (default)
*/
WithTrailingLabel(s, e, t = {}) {
return m(e, { ...t, label: s, labelPosition: "trailing" });
}
}, ge = {
/**
* Create a toggle group with multiple options
*/
createGroup(s, e = {}) {
return s.map(
(t) => N(t.label, t.isOn, {
...e,
onToggle: t.onToggle
})
);
},
/**
* Create exclusive toggle group (radio-like behavior)
*/
createExclusiveGroup(s, e, t, i = {}) {
const r = () => l(e) ? e() : e;
return s.map(
(n) => N(n.label, r() === n.key, {
...i,
onToggle: (o) => {
o && t && t(n.key);
}
})
);
},
/**
* Batch toggle operations
*/
batch: {
/**
* Toggle all items in a group
*/
toggleAll(s, e) {
s.forEach((t) => {
t.onToggle && t.onToggle(e);
});
},
/**
* Get state of all toggles in a group
*/
getStates(s) {
return s.map((e) => l(e.isOn) ? e.isOn() : e.isOn);
},
/**
* Check if all toggles are on
*/
allOn(s) {
return this.getStates(s).every((e) => e);
},
/**
* Check if any toggle is on
*/
anyOn(s) {
return this.getStates(s).some((e) => e);
}
}
};
var ae = Object.defineProperty, le = (s, e, t) => e in s ? ae(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t, b = (s, e, t) => le(s, typeof e != "symbol" ? e + "" : e, t);
class de {
constructor(e) {
this.props = e, b(this, "type", "component"), b(this, "id"), b(this, "mounted", !1), b(this, "cleanup", []), b(this, "isOpen"), b(this, "setIsOpen"), b(this, "searchTerm"), b(this, "setSearchTerm"), b(this, "handleSelection", (o) => {
this.props.onSelectionChange && this.props.onSelectionChange(o), (this.props.variant === "dropdown" || this.props.variant === "menu") && this.setIsOpen(!1), this.setSearchTerm("");
}), b(this, "toggleOpen", () => {
this.isDisabled() || this.setIsOpen(!this.isOpen());
}), this.id = `picker-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const [t, i] = z(!1), [r, n] = z("");
if (this.isOpen = t, this.setIsOpen = i, this.searchTerm = r, this.setSearchTerm = n, typeof window < "u") {
const o = (d) => {
const u = document.getElementById(this.id);
u && !u.contains(d.target) && this.setIsOpen(!1);
};
document.addEventListener("click", o), this.cleanup.push(() => {
document.removeEventListener("click", o);
});
}
}
/**
* Get current selection value
*/
getSelection() {
const { selection: e } = this.props;
return l(e) ? e() : e;
}
/**
* Get current options
*/
getOptions() {
const { options: e } = this.props;
return l(e) ? e() : e;
}
/**
* Filter options based on search term
*/
getFilteredOptions() {
const e = this.getOptions(), t = this.searchTerm().toLowerCase();
return !t || !this.props.searchable ? e : e.filter(
(i) => i.label.toLowerCase().includes(t) || i.description?.toLowerCase().includes(t)
);
}
/**
* Check if picker is disabled
*/
isDisabled() {
const { disabled: e } = this.props;
return typeof e == "boolean" ? e : l(e) ? e() : !1;
}
/**
* Get selected option
*/
getSelectedOption() {
const e = this.getSelection();
return this.getOptions().find((i) => i.value === e) || null;
}
/**
* Get picker size styles
*/
getSizeStyles() {
const { size: e = "medium" } = this.props;
switch (e) {
case "small":
return {
fontSize: "14px",
padding: "6px 12px",
minHeight: "32px"
};
case "large":
return {
fontSize: "18px",
padding: "12px 16px",
minHeight: "48px"
};
default:
return {
fontSize: "16px",
padding: "8px 12px",
minHeight: "40px"
};
}
}
/**
* Render dropdown variant
*/
renderDropdown() {
const e = this.getSelectedOption(), t = this.getFilteredOptions(), i = this.getSizeStyles(), r = this.isOpen();
return a(
"div",
{
id: this.id,
style: {
position: "relative",
display: "inline-block",
minWidth: "200px"
}
},
// Trigger button
a(
"button",
{
type: "button",
onClick: this.toggleOpen,
disabled: this.isDisabled(),
style: {
...i,
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
backgroundColor: "#ffffff",
border: "1px solid #d1d5db",
borderRadius: "6px",
cursor: this.isDisabled() ? "not-allowed" : "pointer",
opacity: this.isDisabled() ? 0.6 : 1,
...r && {
borderColor: this.props.tint || "#007AFF",
boxShadow: `0 0 0 1px ${this.props.tint || "#007AFF"}`
}
}
},
a(
"span",
{
style: {
color: e ? "#1a1a1a" : "#6b7280"
}
},
p(
e?.label || this.props.placeholder || "Select an option"
)
),
a(
"span",
{
style: {
fontSize: "12px",
color: "#6b7280",
transform: r ? "rotate(180deg)" : "rotate(0deg)",
transition: "transform 0.2s"
}
},
p("▼")
)
),
...r ? [
a(
"div",
{
style: {
position: "absolute",
top: "100%",
left: "0",
right: "0",
zIndex: 1e3,
backgroundColor: "#ffffff",
border: "1px solid #d1d5db",
borderRadius: "6px",
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
maxHeight: "200px",
overflowY: "auto",
marginTop: "2px"
}
},
...this.props.searchable ? [
a(
"div",
{
style: {
padding: "8px",
borderBottom: "1px solid #e5e7eb"
}
},
a("input", {
type: "text",
placeholder: "Search options...",
value: this.searchTerm(),
onInput: (n) => {
const o = n.target;
this.setSearchTerm(o.value);
},
style: {
width: "100%",
padding: "6px 8px",
border: "1px solid #d1d5db",
borderRadius: "4px",
fontSize: "14px"
}
})
)
] : [],
...t.map(
(n) => a(
"button",
{
type: "button",
key: String(n.value),
onClick: () => this.handleSelection(n.value),
disabled: n.disabled,
style: {
width: "100%",
padding: "8px 12px",
textAlign: "left",
backgroundColor: n.value === this.getSelection() ? "#f3f4f6" : "transparent",
border: "none",
cursor: n.disabled ? "not-allowed" : "pointer",
opacity: n.disabled ? 0.5 : 1,
fontSize: "14px",
display: "flex",
alignItems: "center",
gap: "8px",
":hover": {
backgroundColor: "#f9fafb"
}
}
},
...n.icon ? [
a(
"span",
{ style: { fontSize: "16px" } },
p(n.icon)
)
] : [],
a(
"div",
{},
a("div", {}, p(n.label)),
...n.description ? [
a(
"div",
{
style: {
fontSize: "12px",
color: "#6b7280",
marginTop: "2px"
}
},
p(n.description)
)
] : []
)
)
),
...t.length === 0 ? [
a(
"div",
{
style: {
padding: "12px",
textAlign: "center",
color: "#6b7280",
fontSize: "14px"
}
},
p("No options found")
)
] : []
)
] : []
);
}
/**
* Render segmented variant
*/
renderSegmented() {
const e = this.getOptions(), t = this.getSelection(), i = this.getSizeStyles();
return a(
"div",
{
style: {
display: "flex",
backgroundColor: "#f3f4f6",
borderRadius: "8px",
padding: "2px"
},
role: "radiogroup",
"aria-label": this.props.accessibilityLabel
},
...e.map(
(r, n) => a(
"button",
{
type: "button",
key: String(r.value),
onClick: () => this.handleSelection(r.value),
disabled: r.disabled || this.isDisabled(),
role: "radio",
"aria-checked": r.value === t,
style: {
...i,
flex: 1,
border: "none",
borderRadius: "6px",
backgroundColor: r.value === t ? "#ffffff" : "transparent",
color: r.value === t ? "#1a1a1a" : "#6b7280",
fontWeight: r.value === t ? "600" : "400",
cursor: r.disabled || this.isDisabled() ? "not-allowed" : "pointer",
opacity: r.disabled || this.isDisabled() ? 0.5 : 1,
boxShadow: r.value === t ? "0 1px 2px rgba(0, 0, 0, 0.1)" : "none",
transition: "all 0.2s",
margin: "0",
...n === 0 && { marginRight: "1px" },
...n === e.length - 1 && { marginLeft: "1px" }
}
},
...r.icon ? [
a(
"span",
{
style: {
marginRight: "6px",
fontSize: "14px"
}
},
p(r.icon)
)
] : [],
p(r.label)
)
)
);
}
/**
* Render wheel variant (simplified)
*/
renderWheel() {
const e = this.getOptions(), t = this.getSelection();
return a(
"select",
{
value: String(t),
onChange: (i) => {
const r = i.target, n = e.find(
(o) => String(o.value) === r.value
);
n && this.handleSelection(n.value);
},
disabled: this.isDisabled(),
style: {
...this.getSizeStyles(),
minWidth: "200px",
backgroundColor: "#ffffff",
border: "1px solid #d1d5db",
borderRadius: "6px",
cursor: this.isDisabled() ? "not-allowed" : "pointer",
opacity: this.isDisabled() ? 0.6 : 1
}
},
...e.map(
(i) => a(
"option",
{
key: String(i.value),
value: String(i.value),
disabled: i.disabled
},
p(i.label)
)
)
);
}
render() {
const {
variant: e = "dropdown",
accessibilityLabel: t,
accessibilityHint: i
} = this.props, r = {
"aria-label": t,
"aria-describedby": i ? `${this.id}-hint` : void 0
};
let n;
switch (e) {
case "segmented":
n = this.renderSegmented();
break;
case "wheel":
n = this.renderWheel();
break;
default:
n = this.renderDropdown();
break;
}
return a(
"div",
r,
n,
...i ? [
a(
"div",
{
id: `${this.id}-hint`,
style: {
fontSize: "12px",
color: "#6b7280",
marginTop: "4px"
}
},
p(i)
)
] : []
);
}
}
function $(s, e, t = {}) {
const i = { ...t, selection: s, options: e }, r = new de(i);
return T(r);
}
const be = {
/**
* Dropdown picker (default)
*/
// biome-ignore lint/suspicious/noExplicitAny: Generic dropdown picker requires flexible type
Dropdown(s, e, t = {}) {
return $(s, e, { ...t, variant: "dropdown" });
},
/**
* Segmented picker
*/
// biome-ignore lint/suspicious/noExplicitAny: Generic segmented picker requires flexible type
Segmented(s, e, t = {}) {
return $(s, e, { ...t, variant: "segmented" });
},
/**
* Wheel picker (native select)
*/
// biome-ignore lint/suspicious/noExplicitAny: Generic wheel picker requires flexible type
Wheel(s, e, t = {}) {
return $(s, e, { ...t, variant: "wheel" });
},
/**
* Menu picker
*/
// biome-ignore lint/suspicious/noExplicitAny: Generic menu picker requires flexible type
Menu(s, e, t = {}) {
return $(s, e, { ...t, variant: "menu" });
}
}, me = {
/**
* Create options from simple array
*/
fromArray(s, e) {
return s.map((t) => ({
value: t,
label: String(e && typeof t == "object" && t !== null ? t[e] : t)
}));
},
/**
* Create options from enum
*/
fromEnum(s) {
return Object.entries(s).map(([e, t]) => ({
value: t,
label: e.replace(/([A-Z])/g, " $1").trim()
}));
},
/**
* Group options by category
*/
grouped(s) {
return s.reduce(
(e, t) => {