@tachui/modifiers
Version:
Essential styling modifiers for tachUI framework
843 lines (842 loc) • 33.6 kB
JavaScript
var T = Object.defineProperty;
var M = (v, l, n) => l in v ? T(v, l, { enumerable: !0, configurable: !0, writable: !0, value: n }) : v[l] = n;
var y = (v, l, n) => M(v, typeof l != "symbol" ? l + "" : l, n);
import { isSignal as u, isComputed as m, createEffect as g, getThemeSignal as w } from "@tachui/core/reactive";
import { ModifierPriority as E } from "@tachui/core/modifiers/types";
import { shouldExpandForInfinity as A, dimensionToCSS as S, isInfinity as C } from "@tachui/core/constants/layout";
class L {
constructor(l) {
this.properties = l;
}
/**
* Helper to resolve reactive properties
*/
resolveReactiveProps(l, n) {
const t = {};
for (const [e, i] of Object.entries(l))
u(i) || m(i), t[e] = i;
return t;
}
/**
* Apply a single style change to an element with reactive support
*/
applyStyleChange(l, n, t) {
if (l instanceof HTMLElement) {
const e = this.toCSSProperty(n);
if (u(t) || m(t))
g(() => {
const i = t(), s = String(i);
if (s.includes("!important")) {
const o = s.replace(/\s*!important\s*$/, "").trim();
l.style.setProperty(e, o, "important");
} else
l.style.setProperty(e, s);
});
else {
const i = String(t);
if (i.includes("!important")) {
const s = i.replace(/\s*!important\s*$/, "").trim();
l.style.setProperty(e, s, "important");
} else
l.style.setProperty(e, i);
}
}
}
/**
* Convert camelCase property to CSS kebab-case
*/
toCSSProperty(l) {
return l.replace(/([A-Z])/g, "-$1").toLowerCase();
}
/**
* Convert value to CSS value string
*/
toCSSValue(l) {
return typeof l == "number" ? `${l}px` : String(l);
}
/**
* Convert value to CSS value string with property-specific handling
*/
toCSSValueForProperty(l, n) {
return typeof n == "number" ? [
"opacity",
"z-index",
"line-height",
"flex-grow",
"flex-shrink",
"order",
"column-count",
"font-weight"
].includes(l) ? String(n) : `${n}px` : ([
"filter",
// CSS filter strings should not be processed
"transform",
// CSS transform strings
"clip-path"
// CSS clip-path strings
].includes(l), String(n));
}
/**
* Apply multiple CSS properties to an element with reactive support
*/
applyStyles(l, n) {
if (l instanceof HTMLElement || l.style) {
const t = (l instanceof HTMLElement, l.style);
for (const [e, i] of Object.entries(n))
if (i !== void 0) {
const s = this.toCSSProperty(e);
if (u(i) || m(i))
g(() => {
const o = i(), r = this.toCSSValueForProperty(
s,
o
);
if (t.setProperty)
if (typeof r == "string" && r.includes("!important")) {
const a = r.replace(/\s*!important\s*$/, "").trim();
t.setProperty(s, a, "important");
} else
t.setProperty(s, r);
else
t[s] = r;
});
else {
const o = this.toCSSValueForProperty(s, i);
if (t.setProperty)
if (typeof o == "string" && o.includes("!important")) {
const r = o.replace(/\s*!important\s*$/, "").trim();
t.setProperty(s, r, "important");
} else
t.setProperty(s, o);
else
t[s] = o;
}
}
}
}
/**
* Add CSS classes to an element
*/
addClasses(l, n) {
l instanceof HTMLElement && l.classList.add(...n);
}
/**
* Remove CSS classes from an element
*/
removeClasses(l, n) {
l instanceof HTMLElement && l.classList.remove(...n);
}
/**
* Create a style computation context
*/
createStyleContext(l, n, t) {
return {
componentId: l,
element: n,
modifiers: t,
signals: /* @__PURE__ */ new Set(),
cleanup: []
};
}
}
class R extends L {
constructor() {
super(...arguments);
y(this, "type", "layout");
y(this, "priority", E.LAYOUT);
}
apply(n, t) {
if (!n.element || !t.element) return;
const e = this.createStyleContext(
t.componentId,
t.element,
[]
), i = this.computeLayoutStyles(
this.properties,
e
);
this.applyStyles(t.element, i);
const s = this.properties;
s.offset && t.element instanceof HTMLElement && this.applyOffsetTransform(t.element, s.offset), s.aspectRatio && t.element instanceof HTMLElement && this.applyAspectRatio(t.element, s.aspectRatio), s.scaleEffect && t.element instanceof HTMLElement && this.applyScaleTransform(t.element, s.scaleEffect), s.position && t.element instanceof HTMLElement && this.applyAbsolutePosition(t.element, s.position), s.zIndex !== void 0 && t.element instanceof HTMLElement && this.applyZIndex(t.element, s.zIndex);
}
applyOffsetTransform(n, t) {
const { x: e, y: i } = t;
if (u(e) || m(e) || u(i) || m(i))
g(() => {
const s = u(e) || m(e) ? e() : e ?? 0, o = u(i) || m(i) ? i() : i ?? 0, r = this.toCSSValue(s), a = this.toCSSValue(o), c = `translate(${r}, ${a})`, h = (n.style.transform || "").split(" ").filter((p) => p && !p.startsWith("translate(")).join(" "), d = h ? `${h} ${c}` : c;
n.style.transform = d;
});
else {
const s = e ?? 0, o = i ?? 0, r = this.toCSSValue(s), a = this.toCSSValue(o), c = `translate(${r}, ${a})`, h = (n.style.transform || "").split(" ").filter((p) => p && !p.startsWith("translate(")).join(" "), d = h ? `${h} ${c}` : c;
n.style.transform = d;
}
}
applyAspectRatio(n, t) {
const { ratio: e, contentMode: i } = t;
e !== void 0 && (u(e) || m(e) ? g(() => {
const s = typeof e == "function" ? e() : e;
n.style.aspectRatio = String(s);
}) : n.style.aspectRatio = String(e), i === "fill" ? n.style.objectFit = "cover" : n.style.objectFit = "contain");
}
// Phase 3 - Epic: Butternut Transform Methods
applyScaleTransform(n, t) {
const { x: e, y: i, anchor: s } = t, o = e ?? 1, r = i ?? o;
if (u(o) || m(o) || u(r) || m(r))
g(() => {
const a = u(o) || m(o) ? o() : o, c = u(r) || m(r) ? r() : r, f = `scale(${a}, ${c})`;
n.style.transformOrigin = this.getTransformOrigin(
s || "center"
);
const d = (n.style.transform || "").replace(/\s*scale\([^)]*\)\s*/g, " ").replace(/\s+/g, " ").trim(), p = d ? `${d} ${f}` : f;
n.style.transform = p;
});
else {
const a = `scale(${o}, ${r})`;
n.style.transformOrigin = this.getTransformOrigin(
s || "center"
);
const f = (n.style.transform || "").replace(/\s*scale\([^)]*\)\s*/g, " ").replace(/\s+/g, " ").trim(), h = f ? `${f} ${a}` : a;
n.style.transform = h;
}
}
applyAbsolutePosition(n, t) {
const { x: e, y: i } = t;
if (n.style.position = "absolute", u(e) || m(e) || u(i) || m(i))
g(() => {
const s = u(e) || m(e) ? e() : e ?? 0, o = u(i) || m(i) ? i() : i ?? 0;
n.style.left = this.toCSSValue(s), n.style.top = this.toCSSValue(o);
});
else {
const s = e ?? 0, o = i ?? 0;
n.style.left = this.toCSSValue(s), n.style.top = this.toCSSValue(o);
}
}
applyZIndex(n, t) {
u(t) || m(t) ? g(() => {
const e = t();
n.style.zIndex = String(e);
}) : n.style.zIndex = String(t);
}
getTransformOrigin(n) {
return {
center: "center center",
top: "center top",
topLeading: "left top",
topTrailing: "right top",
bottom: "center bottom",
bottomLeading: "left bottom",
bottomTrailing: "right bottom",
leading: "left center",
trailing: "right center"
}[n] || "center center";
}
computeLayoutStyles(n, t) {
const e = {};
if (n.frame) {
const i = n.frame, s = A(i);
if (Object.assign(e, s.cssProps), i.width !== void 0) {
const o = S(i.width);
o !== void 0 && !C(i.width) && !s.expandWidth && (e.width = o);
}
if (i.height !== void 0) {
const o = S(i.height);
o !== void 0 && !C(i.height) && !s.expandHeight && (e.height = o);
}
if (i.minWidth !== void 0) {
const o = S(i.minWidth);
o !== void 0 && (e.minWidth = o);
}
if (i.maxWidth !== void 0 && !C(i.maxWidth)) {
const o = S(i.maxWidth);
o !== void 0 && (e.maxWidth = o);
} else C(i.maxWidth) && (e.maxWidth = "none", e.flexGrow = "1 !important", e.flexShrink = "1 !important", e.flexBasis = "0% !important", e.alignSelf = "stretch !important");
if (i.minHeight !== void 0) {
const o = S(i.minHeight);
o !== void 0 && (e.minHeight = o);
}
if (i.maxHeight !== void 0 && !C(i.maxHeight)) {
const o = S(i.maxHeight);
o !== void 0 && (e.maxHeight = o);
} else C(i.maxHeight) && (e.maxHeight = "none", e.flexGrow = "1 !important", e.flexShrink = "1 !important", e.flexBasis = "0% !important", e.alignSelf = "stretch !important");
}
if (n.padding !== void 0)
if (typeof n.padding == "number")
e.padding = this.toCSSValue(n.padding);
else {
const i = n.padding;
i.top !== void 0 && (e.paddingTop = this.toCSSValue(i.top)), i.right !== void 0 && (e.paddingRight = this.toCSSValue(i.right)), i.bottom !== void 0 && (e.paddingBottom = this.toCSSValue(i.bottom)), i.left !== void 0 && (e.paddingLeft = this.toCSSValue(i.left));
}
if (n.margin !== void 0)
if (typeof n.margin == "number")
e.margin = this.toCSSValue(n.margin);
else {
const i = n.margin;
i.top !== void 0 && (e.marginTop = this.toCSSValue(i.top)), i.right !== void 0 && (e.marginRight = this.toCSSValue(i.right)), i.bottom !== void 0 && (e.marginBottom = this.toCSSValue(i.bottom)), i.left !== void 0 && (e.marginLeft = this.toCSSValue(i.left));
}
if (n.alignment)
switch (n.alignment) {
case "leading":
e.textAlign = "left", e.alignItems = "flex-start";
break;
case "center":
e.textAlign = "center", e.alignItems = "center";
break;
case "trailing":
e.textAlign = "right", e.alignItems = "flex-end";
break;
case "top":
e.alignItems = "flex-start";
break;
case "bottom":
e.alignItems = "flex-end";
break;
}
if (n.layoutPriority !== void 0) {
const i = Number(n.layoutPriority);
i > 0 ? (e.flexShrink = "0", e.flexGrow = String(Math.max(1, i / 10)), e.zIndex = String(i), e.gridRowEnd = `span ${String(Math.min(10, Math.max(1, Math.ceil(i / 10))))}`, e.gridColumnEnd = `span ${String(Math.min(10, Math.max(1, Math.ceil(i / 10))))}`) : i === 0 ? (e.flexShrink = "1", e.flexGrow = "1") : (e.flexShrink = String(Math.abs(i)), e.flexGrow = "0", e.zIndex = String(i)), e && typeof e == "object" && "setProperty" in e && e.setProperty("--layout-priority", String(i));
}
if (n.offset, n.aspectRatio) {
const { ratio: i, contentMode: s } = n.aspectRatio;
i !== void 0 && (e.aspectRatio = typeof i == "number" ? String(i) : i, s === "fill" ? e.objectFit = "cover" : e.objectFit = "contain");
}
if (n.fixedSize) {
const { horizontal: i, vertical: s } = n.fixedSize;
i && (e.flexShrink = "0", e.width = "max-content"), s && (e.flexShrink = "0", e.height = "max-content");
}
return e;
}
}
class x extends L {
constructor() {
super(...arguments);
y(this, "type", "appearance");
y(this, "priority", E.APPEARANCE);
}
apply(n, t) {
if (!n.element || !t.element)
return;
const e = this.createStyleContext(
t.componentId,
t.element,
[]
), i = this.resolveReactiveProps(
this.properties,
e
);
this.applyAssetBasedStyles(t.element, i);
const s = this.computeAppearanceStyles(i);
this.applyStyles(t.element, s), this.applyAttributes(t.element, i);
}
/**
* Apply Asset-based styles with theme reactivity
*/
applyAssetBasedStyles(n, t) {
const e = w();
t.foregroundColor && this.isAsset(t.foregroundColor) && g(() => {
e();
const i = t.foregroundColor.resolve();
this.applyStyleChange(n, "color", i);
}), t.backgroundColor && this.isAsset(t.backgroundColor) && g(() => {
e();
const i = t.backgroundColor.resolve();
this.applyStyleChange(n, "backgroundColor", i);
}), t.border?.color && this.isAsset(t.border.color) && g(() => {
e();
const i = t.border.color.resolve();
this.applyStyleChange(n, "borderColor", i);
});
}
/**
* Check if a value is an Asset object (including Asset proxies)
*/
isAsset(n) {
return n != null && typeof n == "object" && "resolve" in n && typeof n.resolve == "function";
}
computeAppearanceStyles(n) {
const t = {};
if (n.foregroundColor && !this.isAsset(n.foregroundColor) && (t.color = n.foregroundColor), n.backgroundColor && !this.isAsset(n.backgroundColor) && (t.backgroundColor = n.backgroundColor), n.opacity !== void 0 && (t.opacity = n.opacity), n.font) {
const i = n.font;
i.family && (typeof i.family == "object" && i.family !== null && "resolve" in i.family ? t.fontFamily = i.family.resolve() : t.fontFamily = i.family), i.size && (t.fontSize = this.toCSSValue(i.size)), i.weight && (t.fontWeight = String(i.weight)), i.style && (t.fontStyle = i.style);
}
if (n.cornerRadius !== void 0 && (t.borderRadius = this.toCSSValue(n.cornerRadius)), n.border) {
const i = n.border;
i.width !== void 0 && (t.borderWidth = this.toCSSValue(i.width)), i.color && !this.isAsset(i.color) && (t.borderColor = i.color), i.style && (t.borderStyle = i.style);
}
if (n.shadow) {
const i = n.shadow, s = i.x || 0, o = i.y || 0, r = i.radius || 0, a = i.color || "rgba(0,0,0,0.25)";
t.boxShadow = `${s}px ${o}px ${r}px ${a}`;
}
if (n.clipped && (t.overflow = "hidden"), n.clipShape) {
const { shape: i, parameters: s } = n.clipShape;
switch (i) {
case "circle":
t.clipPath = "circle(50%)";
break;
case "ellipse": {
const o = s?.radiusX || "50%", r = s?.radiusY || "50%";
t.clipPath = `ellipse(${o} ${r} at center)`;
break;
}
case "rect": {
const o = s?.inset || 0;
t.clipPath = `inset(${o}px)`;
break;
}
case "polygon": {
const o = s?.points || "0% 0%, 100% 0%, 100% 100%, 0% 100%";
t.clipPath = `polygon(${o})`;
break;
}
}
}
const e = [];
return n.blur !== void 0 && e.push(`blur(${n.blur}px)`), n.brightness !== void 0 && e.push(`brightness(${n.brightness})`), n.contrast !== void 0 && e.push(`contrast(${n.contrast})`), n.saturation !== void 0 && e.push(`saturate(${n.saturation})`), n.hueRotation !== void 0 && e.push(`hue-rotate(${n.hueRotation}deg)`), n.grayscale !== void 0 && e.push(`grayscale(${n.grayscale})`), n.colorInvert !== void 0 && e.push(`invert(${n.colorInvert})`), e.length > 0 && (t.filter = e.join(" ")), t;
}
/**
* Apply HTML attributes (ARIA, role, data attributes, etc.)
*/
applyAttributes(n, t) {
if (!n) return;
const e = this.findComponentFromElement(n);
t.role !== void 0 && (n.setAttribute("role", String(t.role)), e?.props && (e.props.role = String(t.role))), t["aria-label"] !== void 0 && (n.setAttribute("aria-label", String(t["aria-label"])), e?.props && (e.props["aria-label"] = String(t["aria-label"]))), t["aria-live"] !== void 0 && (n.setAttribute("aria-live", String(t["aria-live"])), e?.props && (e.props["aria-live"] = String(t["aria-live"]))), t["aria-describedby"] !== void 0 && (n.setAttribute(
"aria-describedby",
String(t["aria-describedby"])
), e?.props && (e.props["aria-describedby"] = String(t["aria-describedby"]))), t["aria-modal"] !== void 0 && (n.setAttribute("aria-modal", String(t["aria-modal"])), e?.props && (e.props["aria-modal"] = String(t["aria-modal"]))), t["aria-hidden"] !== void 0 && (n.setAttribute("aria-hidden", String(t["aria-hidden"])), e?.props && (e.props["aria-hidden"] = String(t["aria-hidden"]))), t.navigationTitle !== void 0 && (n.setAttribute(
"data-navigation-title",
String(t.navigationTitle)
), e?.props && (e.props.navigationTitle = String(t.navigationTitle))), t.navigationBarHidden !== void 0 && (n.setAttribute(
"data-navigation-bar-hidden",
String(t.navigationBarHidden)
), e?.props && (e.props.navigationBarHidden = t.navigationBarHidden), t.navigationBarHidden && (n.setAttribute("aria-hidden", "true"), e?.props && (e.props["aria-hidden"] = "true"))), t.navigationBarItems !== void 0 && (n.setAttribute(
"data-navigation-bar-items",
JSON.stringify(t.navigationBarItems)
), e?.props && (e.props.navigationBarItems = t.navigationBarItems));
}
findComponentFromElement(n) {
return n._tachui_component || null;
}
}
class I extends L {
constructor() {
super(...arguments);
y(this, "type", "interaction");
y(this, "priority", E.INTERACTION);
}
apply(n, t) {
if (!t.element) return;
const e = this.properties;
if (e.onTap && t.element.addEventListener("click", e.onTap), e.onHover && (t.element.addEventListener("mouseenter", () => e.onHover(!0)), t.element.addEventListener("mouseleave", () => e.onHover(!1))), e.onMouseEnter && t.element.addEventListener("mouseenter", e.onMouseEnter), e.onMouseLeave && t.element.addEventListener("mouseleave", e.onMouseLeave), e.onMouseDown && t.element.addEventListener("mousedown", e.onMouseDown), e.onMouseUp && t.element.addEventListener("mouseup", e.onMouseUp), e.onDragStart && t.element.addEventListener("dragstart", e.onDragStart), e.onDragOver && t.element.addEventListener("dragover", e.onDragOver), e.onDragLeave && t.element.addEventListener("dragleave", e.onDragLeave), e.onDrop && t.element.addEventListener("drop", e.onDrop), e.onDoubleClick && t.element.addEventListener("dblclick", e.onDoubleClick), e.onContextMenu && t.element.addEventListener("contextmenu", e.onContextMenu), e.onFocus && (t.element.addEventListener("focus", () => e.onFocus(!0)), t.element.addEventListener("blur", () => e.onFocus(!1))), e.onBlur && t.element.addEventListener("blur", () => e.onBlur(!1)), e.onKeyPress && t.element.addEventListener("keypress", e.onKeyPress), e.onKeyDown && t.element.addEventListener("keydown", e.onKeyDown), e.onKeyUp && t.element.addEventListener("keyup", e.onKeyUp), e.onTouchStart && t.element.addEventListener("touchstart", e.onTouchStart, {
passive: !0
}), e.onTouchMove && t.element.addEventListener("touchmove", e.onTouchMove, {
passive: !0
}), e.onTouchEnd && t.element.addEventListener("touchend", e.onTouchEnd, {
passive: !0
}), e.onTouchCancel && t.element.addEventListener("touchcancel", e.onTouchCancel, {
passive: !0
}), e.onSwipeLeft || e.onSwipeRight) {
let i = 0, s = 0;
t.element.addEventListener(
"touchstart",
(o) => {
const a = o.touches[0];
i = a.clientX, s = a.clientY;
},
{ passive: !0 }
), t.element.addEventListener(
"touchend",
(o) => {
const a = o.changedTouches[0], c = a.clientX - i, f = a.clientY - s;
Math.abs(c) > Math.abs(f) && Math.abs(c) > 50 && (c < 0 && e.onSwipeLeft ? e.onSwipeLeft() : c > 0 && e.onSwipeRight && e.onSwipeRight());
},
{ passive: !0 }
);
}
e.onScroll && t.element.addEventListener("scroll", e.onScroll, {
passive: !0
}), e.onWheel && t.element.addEventListener("wheel", e.onWheel, {
passive: !1
}), e.onInput && t.element.addEventListener("input", e.onInput), e.onChange && t.element.addEventListener("change", (i) => {
const s = i.target, o = s.value || s.textContent || "";
e.onChange(o, i);
}), e.onCopy && t.element.addEventListener("copy", e.onCopy), e.onCut && t.element.addEventListener("cut", e.onCut), e.onPaste && t.element.addEventListener("paste", e.onPaste), e.onSelect && t.element.addEventListener("select", e.onSelect), e.disabled !== void 0 && t.element instanceof HTMLElement && (e.disabled ? (t.element.setAttribute("disabled", "true"), t.element.style.pointerEvents = "none", t.element.style.opacity = "0.6") : (t.element.removeAttribute("disabled"), t.element.style.pointerEvents = "", t.element.style.opacity = "")), e.draggable !== void 0 && t.element instanceof HTMLElement && (t.element.draggable = e.draggable), e.accessibilityLabel && t.element.setAttribute("aria-label", e.accessibilityLabel), e.accessibilityHint && t.element.setAttribute("aria-describedby", e.accessibilityHint), e.onLongPressGesture && this.setupLongPressGesture(t.element, e.onLongPressGesture), e.keyboardShortcut && this.setupKeyboardShortcut(t.element, e.keyboardShortcut), e.focused !== void 0 && this.setupFocusManagement(t.element, e.focused), e.focusable && this.setupFocusable(t.element, e.focusable), e.onContinuousHover && this.setupContinuousHover(t.element, e.onContinuousHover), e.allowsHitTesting !== void 0 && this.setupHitTesting(t.element, e.allowsHitTesting);
}
// Phase 4 Advanced Gesture Methods
/**
* Setup long press gesture with timing and distance constraints
*/
setupLongPressGesture(n, t) {
const e = t.minimumDuration ?? 500, i = t.maximumDistance ?? 10;
let s, o = null, r = !1;
const a = () => {
s && (clearTimeout(s), s = void 0), r && t.onPressingChanged && t.onPressingChanged(!1), r = !1, o = null;
}, c = (p) => {
const b = p;
o = { x: b.clientX, y: b.clientY }, r = !0, t.onPressingChanged && t.onPressingChanged(!0), s = window.setTimeout(() => {
r && o && (t.perform(), a());
}, e);
}, f = (p) => {
const b = p;
if (!o || !r) return;
Math.sqrt(
Math.pow(b.clientX - o.x, 2) + Math.pow(b.clientY - o.y, 2)
) > i && a();
}, h = () => {
a();
}, d = () => {
a();
};
n.addEventListener("pointerdown", c), n.addEventListener("pointermove", f), n.addEventListener("pointerup", h), n.addEventListener(
"pointercancel",
d
), n._longPressCleanup = a;
}
/**
* Setup keyboard shortcut handling
*/
setupKeyboardShortcut(n, t) {
const e = t.modifiers ?? [], i = (s) => {
const o = {
cmd: e.includes("cmd") || e.includes("meta"),
ctrl: e.includes("ctrl"),
shift: e.includes("shift"),
alt: e.includes("alt")
}, r = {
cmd: s.metaKey || s.ctrlKey,
// Handle both Mac (meta) and PC (ctrl)
ctrl: s.ctrlKey,
shift: s.shiftKey,
alt: s.altKey
}, a = s.key.toLowerCase() === t.key.toLowerCase(), c = Object.entries(o).every(
([f, h]) => h === r[f]
);
a && c && (s.preventDefault(), t.action());
};
document.addEventListener("keydown", i), n._keyboardShortcutCleanup = () => {
document.removeEventListener("keydown", i);
};
}
/**
* Setup focus management with reactive binding
*/
setupFocusManagement(n, t) {
if (!(n instanceof HTMLElement)) return;
const e = n;
e.hasAttribute("tabindex") || e.setAttribute("tabindex", "0"), u(t) || m(t) ? g(() => {
t() ? e.focus() : e.blur();
}) : t && e.focus();
}
/**
* Setup focusable behavior
*/
setupFocusable(n, t) {
if (!(n instanceof HTMLElement)) return;
const e = n;
t.isFocusable === !1 ? e.setAttribute("tabindex", "-1") : e.hasAttribute("tabindex") || e.setAttribute("tabindex", "0"), t.interactions?.includes("activate") && e.addEventListener("keydown", (i) => {
(i.key === "Enter" || i.key === " ") && (i.preventDefault(), e.click());
}), t.interactions?.includes("edit") && (e.setAttribute("role", "textbox"), e.setAttribute("contenteditable", "true"));
}
/**
* Setup continuous hover tracking with coordinates
*/
setupContinuousHover(n, t) {
const e = t.coordinateSpace ?? "local", i = (o) => {
const r = o;
let a, c;
if (e === "local") {
const f = n.getBoundingClientRect();
a = r.clientX - f.left, c = r.clientY - f.top;
} else
a = r.clientX, c = r.clientY;
t.perform({ x: a, y: c });
}, s = () => {
t.perform(null);
};
n.addEventListener("mousemove", i), n.addEventListener("mouseleave", s), n._continuousHoverCleanup = () => {
n.removeEventListener("mousemove", i), n.removeEventListener(
"mouseleave",
s
);
};
}
/**
* Setup hit testing control
*/
setupHitTesting(n, t) {
n instanceof HTMLElement && (n.style.pointerEvents = t ? "" : "none");
}
}
class O extends L {
constructor() {
super(...arguments);
y(this, "type", "animation");
y(this, "priority", E.ANIMATION);
}
apply(n, t) {
if (!t.element) return;
const e = this.properties;
if (e.transition) {
const i = e.transition, s = i.property || "all", o = i.duration || 300, r = i.easing || "ease", a = i.delay || 0;
t.element instanceof HTMLElement && (t.element.style.transition = `${s} ${o}ms ${r} ${a}ms`);
}
if (e.animation && t.element instanceof HTMLElement) {
const i = e.animation;
if (i.keyframes) {
const s = `tachui-animation-${t.componentId}-${Date.now()}`, o = this.createKeyframeRule(
s,
i.keyframes
);
this.addKeyframesToStylesheet(o);
const r = i.duration || 1e3, a = i.easing || "ease", c = i.iterations || 1, f = i.direction || "normal";
t.element.style.animation = `${s} ${r}ms ${a} ${c} ${f}`;
}
}
if (e.transform && t.element instanceof HTMLElement && (u(e.transform) || m(e.transform) ? g(() => {
const i = e.transform();
t.element instanceof HTMLElement && (t.element.style.transform = i);
}) : t.element.style.transform = e.transform), e.rotationEffect && t.element instanceof HTMLElement) {
const { angle: i, anchor: s } = e.rotationEffect, r = {
center: "50% 50%",
top: "50% 0%",
topLeading: "0% 0%",
topTrailing: "100% 0%",
bottom: "50% 100%",
bottomLeading: "0% 100%",
bottomTrailing: "100% 100%",
leading: "0% 50%",
trailing: "100% 50%"
}[s || "center"] || "50% 50%", a = `rotate(${i}deg)`;
if (u(i) || m(i))
g(() => {
const f = `rotate(${typeof i == "function" ? i() : i}deg)`;
if (t.element instanceof HTMLElement) {
t.element.style.transformOrigin = r;
const d = (t.element.style.transform || "").split(" ").filter((b) => b && !b.startsWith("rotate(")).join(" "), p = d ? `${d} ${f}` : f;
t.element.style.transform = p;
}
});
else if (t.element instanceof HTMLElement) {
t.element.style.transformOrigin = r;
const f = (t.element.style.transform || "").split(" ").filter((d) => d && !d.startsWith("rotate(")).join(" "), h = f ? `${f} ${a}` : a;
t.element.style.transform = h;
}
}
e.overlay && t.element instanceof HTMLElement && this.applyOverlay(t.element, e.overlay, t);
}
applyOverlay(n, t, e) {
const { content: i, alignment: s = "center" } = t;
(n.style.position === "" || n.style.position === "static") && (n.style.position = "relative");
const o = document.createElement("div");
o.style.position = "absolute", o.style.pointerEvents = "none";
const r = this.getOverlayAlignment(s);
if (Object.assign(o.style, r), typeof i == "function") {
const a = i();
if (a && typeof a.render == "function") {
const c = a.render();
c.element && o.appendChild(c.element);
}
} else if (i && typeof i.render == "function") {
const a = i.render();
a.element && o.appendChild(a.element);
} else i instanceof HTMLElement && o.appendChild(i);
n.appendChild(o);
}
getOverlayAlignment(n) {
const t = {
center: {
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)"
},
top: {
top: "0",
left: "50%",
transform: "translateX(-50%)"
},
bottom: {
bottom: "0",
left: "50%",
transform: "translateX(-50%)"
},
leading: {
top: "50%",
left: "0",
transform: "translateY(-50%)"
},
trailing: {
top: "50%",
right: "0",
transform: "translateY(-50%)"
},
topLeading: {
top: "0",
left: "0"
},
topTrailing: {
top: "0",
right: "0"
},
bottomLeading: {
bottom: "0",
left: "0"
},
bottomTrailing: {
bottom: "0",
right: "0"
}
};
return t[n] || t.center;
}
createKeyframeRule(n, t) {
let e = `@keyframes ${n} {
`;
for (const [i, s] of Object.entries(t)) {
e += ` ${i} {
`;
for (const [o, r] of Object.entries(s)) {
const a = this.toCSSProperty(o);
e += ` ${a}: ${r};
`;
}
e += ` }
`;
}
return e += "}", e;
}
addKeyframesToStylesheet(n) {
let t = document.querySelector(
"#tachui-animations"
);
t || (t = document.createElement("style"), t.id = "tachui-animations", document.head.appendChild(t)), t.appendChild(document.createTextNode(n));
}
}
class D extends L {
constructor() {
super(...arguments);
y(this, "type", "lifecycle");
y(this, "priority", E.CUSTOM);
y(this, "activeAbortController");
// Cleanup helpers
y(this, "cleanupFunctions", []);
}
apply(n, t) {
if (!t.element) return;
const e = this.properties;
this.activeAbortController && this.activeAbortController.abort(), (e.onAppear || e.onDisappear) && this.setupLifecycleObserver(t.element, e), e.task && this.setupTask(t, e.task), e.refreshable && this.setupRefreshable(t.element, e.refreshable);
}
setupLifecycleObserver(n, t) {
const e = new IntersectionObserver(
(i) => {
i.forEach((s) => {
s.isIntersecting && t.onAppear ? t.onAppear() : !s.isIntersecting && t.onDisappear && t.onDisappear();
});
},
{
threshold: 0.1,
// Trigger when 10% of element is visible
rootMargin: "10px"
// Add some margin for better UX
}
);
e.observe(n), this.addCleanup(() => {
e.disconnect();
});
}
setupTask(n, t) {
if (!t) return;
this.activeAbortController = new AbortController();
const { signal: e } = this.activeAbortController;
(async () => {
try {
if (e.aborted) return;
const s = t.operation();
s instanceof Promise && await s;
} catch (s) {
if (e.aborted) return;
console.error("TachUI Task Error:", s);
}
})(), this.addCleanup(() => {
this.activeAbortController && this.activeAbortController.abort();
});
}
setupRefreshable(n, t) {
if (!t) return;
let e = !1, i = 0, s = 0;
const o = 70, r = document.createElement("div");
if (r.style.cssText = `
position: absolute;
top: -50px;
left: 50%;
transform: translateX(-50%);
width: 30px;
height: 30px;
border: 2px solid #ccc;
border-top: 2px solid #007AFF;
border-radius: 50%;
animation: tachui-spin 1s linear infinite;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 1000;
`, !document.querySelector("#tachui-refresh-styles")) {
const d = document.createElement("style");
d.id = "tachui-refresh-styles", d.textContent = `
@keyframes tachui-spin {
0% { transform: translateX(-50%) rotate(0deg); }
100% { transform: translateX(-50%) rotate(360deg); }
}
`, document.head.appendChild(d);
}
const a = n.parentElement || n;
a instanceof HTMLElement && (a.style.position = "relative", a.appendChild(r));
const c = (d) => {
e || (s = d.touches[0].clientY);
}, f = (d) => {
if (e) return;
const p = d.touches[0].clientY;
if (i = Math.max(0, p - s), i > 20) {
const b = Math.min(1, i / o);
r.style.opacity = String(b);
}
i > 0 && n.scrollTop === 0 && d.preventDefault();
}, h = async () => {
if (!e)
if (i > o) {
e = !0, r.style.opacity = "1";
try {
await t.onRefresh();
} catch (d) {
console.error("Refresh error:", d);
} finally {
e = !1, r.style.opacity = "0", i = 0;
}
} else
r.style.opacity = "0", i = 0;
};
n.addEventListener("touchstart", c, {
passive: !0
}), n.addEventListener("touchmove", f, {
passive: !1
}), n.addEventListener("touchend", h, {
passive: !0
}), this.addCleanup(() => {
n.removeEventListener(
"touchstart",
c
), n.removeEventListener("touchmove", f), n.removeEventListener("touchend", h), r.parentElement && r.parentElement.removeChild(r);
});
}
addCleanup(n) {
this.cleanupFunctions.push(n);
}
cleanup() {
this.cleanupFunctions.forEach((n) => n()), this.cleanupFunctions = [];
}
}
export {
O as AnimationModifier,
x as AppearanceModifier,
L as BaseModifier,
I as InteractionModifier,
R as LayoutModifier,
D as LifecycleModifier
};
//# sourceMappingURL=base.js.map