@pyreon/kinetic 0.12.13 → 0.12.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +55 -47
- package/lib/index.js.map +1 -1
- package/package.json +11 -5
- package/src/__tests__/kinetic.browser.test.tsx +58 -0
- package/src/kinetic/TransitionItem.tsx +29 -45
- package/src/kinetic/TransitionRenderer.tsx +18 -27
- package/src/utils.ts +7 -3
package/lib/index.js
CHANGED
|
@@ -297,11 +297,16 @@ const removeClasses = (el, classes) => {
|
|
|
297
297
|
/**
|
|
298
298
|
* Executes callback after two animation frames (double-rAF).
|
|
299
299
|
* Ensures the browser paints the current state before applying changes,
|
|
300
|
-
* which is required for CSS transitions to trigger.
|
|
300
|
+
* which is required for CSS transitions to trigger. Returns 0 on SSR —
|
|
301
|
+
* the typeof-window guard makes the SSR-safety contract explicit (callers
|
|
302
|
+
* are always browser-only via `onMount`, but the rule can't AST-trace it).
|
|
301
303
|
*/
|
|
302
|
-
const nextFrame = (callback) =>
|
|
303
|
-
requestAnimationFrame
|
|
304
|
-
|
|
304
|
+
const nextFrame = (callback) => {
|
|
305
|
+
if (typeof requestAnimationFrame === "undefined") return 0;
|
|
306
|
+
return requestAnimationFrame(() => {
|
|
307
|
+
requestAnimationFrame(callback);
|
|
308
|
+
});
|
|
309
|
+
};
|
|
305
310
|
/** Merges two CSSProperties objects, with `b` taking precedence. */
|
|
306
311
|
const mergeStyles = (a, b) => {
|
|
307
312
|
if (!a && !b) return void 0;
|
|
@@ -374,33 +379,36 @@ const applyReducedMotion$1 = (stage, callbacks, complete) => {
|
|
|
374
379
|
*
|
|
375
380
|
* Uses cloneVNode to inject ref onto the child — the child must accept ref.
|
|
376
381
|
*/
|
|
377
|
-
const TransitionItem = (
|
|
382
|
+
const TransitionItem = (props) => {
|
|
383
|
+
const appear = props.appear ?? false;
|
|
384
|
+
const unmount = props.unmount ?? true;
|
|
385
|
+
const timeout = props.timeout ?? 5e3;
|
|
378
386
|
const reducedMotion = useReducedMotion();
|
|
379
387
|
const { stage, ref: stateRef, shouldMount, complete } = useTransitionState({
|
|
380
|
-
show,
|
|
388
|
+
show: props.show,
|
|
381
389
|
appear
|
|
382
390
|
});
|
|
383
391
|
const elementRef = createRef();
|
|
384
|
-
const mergedRef = mergeRefs(elementRef, stateRef, children.props?.ref);
|
|
392
|
+
const mergedRef = mergeRefs(elementRef, stateRef, props.children.props?.ref);
|
|
385
393
|
const callbacks = {
|
|
386
|
-
onEnter,
|
|
387
|
-
onAfterEnter,
|
|
388
|
-
onLeave,
|
|
389
|
-
onAfterLeave
|
|
394
|
+
onEnter: props.onEnter,
|
|
395
|
+
onAfterEnter: props.onAfterEnter,
|
|
396
|
+
onLeave: props.onLeave,
|
|
397
|
+
onAfterLeave: props.onAfterLeave
|
|
390
398
|
};
|
|
391
399
|
const transitionConfig = {
|
|
392
|
-
enter,
|
|
393
|
-
enterFrom,
|
|
394
|
-
enterTo,
|
|
395
|
-
leave,
|
|
396
|
-
leaveFrom,
|
|
397
|
-
leaveTo,
|
|
398
|
-
enterStyle,
|
|
399
|
-
enterToStyle,
|
|
400
|
-
enterTransition,
|
|
401
|
-
leaveStyle,
|
|
402
|
-
leaveToStyle,
|
|
403
|
-
leaveTransition
|
|
400
|
+
enter: props.enter,
|
|
401
|
+
enterFrom: props.enterFrom,
|
|
402
|
+
enterTo: props.enterTo,
|
|
403
|
+
leave: props.leave,
|
|
404
|
+
leaveFrom: props.leaveFrom,
|
|
405
|
+
leaveTo: props.leaveTo,
|
|
406
|
+
enterStyle: props.enterStyle,
|
|
407
|
+
enterToStyle: props.enterToStyle,
|
|
408
|
+
enterTransition: props.enterTransition,
|
|
409
|
+
leaveStyle: props.leaveStyle,
|
|
410
|
+
leaveToStyle: props.leaveToStyle,
|
|
411
|
+
leaveTransition: props.leaveTransition
|
|
404
412
|
};
|
|
405
413
|
useAnimationEnd({
|
|
406
414
|
ref: elementRef,
|
|
@@ -430,17 +438,17 @@ const TransitionItem = ({ show, appear = false, unmount = true, timeout = 5e3, e
|
|
|
430
438
|
return () => cancelAnimationFrame(frameId);
|
|
431
439
|
}
|
|
432
440
|
if (currentStage === "entered") {
|
|
433
|
-
removeClasses(el, enter);
|
|
441
|
+
removeClasses(el, props.enter);
|
|
434
442
|
el.style.transition = "";
|
|
435
443
|
}
|
|
436
444
|
}, { immediate: true });
|
|
437
445
|
return /* @__PURE__ */ jsx(Show, {
|
|
438
446
|
when: shouldMount,
|
|
439
|
-
fallback: unmount ? null : cloneVNode(children, {
|
|
447
|
+
fallback: unmount ? null : cloneVNode(props.children, {
|
|
440
448
|
ref: mergedRef,
|
|
441
|
-
style: mergeStyles(children.props?.style, { display: "none" })
|
|
449
|
+
style: mergeStyles(props.children.props?.style, { display: "none" })
|
|
442
450
|
}),
|
|
443
|
-
children: cloneVNode(children, { ref: mergedRef })
|
|
451
|
+
children: cloneVNode(props.children, { ref: mergedRef })
|
|
444
452
|
});
|
|
445
453
|
};
|
|
446
454
|
|
|
@@ -610,22 +618,22 @@ const applyReducedMotion = (stage, cbs, complete) => {
|
|
|
610
618
|
* Renders a single element with CSS transition enter/exit animation.
|
|
611
619
|
* Uses h(config.tag) — no cloneElement needed.
|
|
612
620
|
*/
|
|
613
|
-
const TransitionRenderer = (
|
|
621
|
+
const TransitionRenderer = (props) => {
|
|
614
622
|
const reducedMotion = useReducedMotion();
|
|
615
623
|
const { stage, ref: stateRef, shouldMount, complete } = useTransitionState({
|
|
616
|
-
show,
|
|
617
|
-
appear: appear ?? config.appear ?? false
|
|
624
|
+
show: props.show,
|
|
625
|
+
appear: props.appear ?? props.config.appear ?? false
|
|
618
626
|
});
|
|
619
627
|
const elementRef = createRef();
|
|
620
628
|
const mergedRef = mergeRefs(elementRef, stateRef);
|
|
621
|
-
const effectiveUnmount = unmount ?? config.unmount ?? true;
|
|
629
|
+
const effectiveUnmount = props.unmount ?? props.config.unmount ?? true;
|
|
622
630
|
useAnimationEnd({
|
|
623
631
|
ref: elementRef,
|
|
624
632
|
active: () => (stage() === "entering" || stage() === "leaving") && !reducedMotion(),
|
|
625
|
-
timeout: timeout ?? config.timeout ?? 5e3,
|
|
633
|
+
timeout: props.timeout ?? props.config.timeout ?? 5e3,
|
|
626
634
|
onEnd: () => {
|
|
627
|
-
if (stage() === "entering") callbacks.onAfterEnter?.();
|
|
628
|
-
else if (stage() === "leaving") callbacks.onAfterLeave?.();
|
|
635
|
+
if (stage() === "entering") props.callbacks.onAfterEnter?.();
|
|
636
|
+
else if (stage() === "leaving") props.callbacks.onAfterLeave?.();
|
|
629
637
|
complete();
|
|
630
638
|
}
|
|
631
639
|
});
|
|
@@ -633,38 +641,38 @@ const TransitionRenderer = ({ config, htmlProps, show, appear, unmount, timeout,
|
|
|
633
641
|
const el = elementRef.current;
|
|
634
642
|
if (!el) return;
|
|
635
643
|
if (reducedMotion()) {
|
|
636
|
-
applyReducedMotion(currentStage, callbacks, complete);
|
|
644
|
+
applyReducedMotion(currentStage, props.callbacks, complete);
|
|
637
645
|
return;
|
|
638
646
|
}
|
|
639
647
|
if (currentStage === "entering") {
|
|
640
|
-
callbacks.onEnter?.();
|
|
641
|
-
const frameId = applyEnter(el, config);
|
|
648
|
+
props.callbacks.onEnter?.();
|
|
649
|
+
const frameId = applyEnter(el, props.config);
|
|
642
650
|
return () => cancelAnimationFrame(frameId);
|
|
643
651
|
}
|
|
644
652
|
if (currentStage === "leaving") {
|
|
645
|
-
callbacks.onLeave?.();
|
|
646
|
-
const frameId = applyLeave(el, config);
|
|
653
|
+
props.callbacks.onLeave?.();
|
|
654
|
+
const frameId = applyLeave(el, props.config);
|
|
647
655
|
return () => cancelAnimationFrame(frameId);
|
|
648
656
|
}
|
|
649
657
|
if (currentStage === "entered") {
|
|
650
|
-
removeClasses(el, config.enter);
|
|
658
|
+
removeClasses(el, props.config.enter);
|
|
651
659
|
el.style.transition = "";
|
|
652
660
|
}
|
|
653
661
|
}, { immediate: true });
|
|
654
662
|
return /* @__PURE__ */ jsx(Show, {
|
|
655
663
|
when: shouldMount,
|
|
656
|
-
fallback: effectiveUnmount ? null : h(config.tag, {
|
|
664
|
+
fallback: effectiveUnmount ? null : h(props.config.tag, {
|
|
657
665
|
ref: mergedRef,
|
|
658
|
-
...htmlProps,
|
|
666
|
+
...props.htmlProps,
|
|
659
667
|
style: {
|
|
660
|
-
...htmlProps.style ?? {},
|
|
668
|
+
...props.htmlProps.style ?? {},
|
|
661
669
|
display: "none"
|
|
662
670
|
}
|
|
663
|
-
}, children),
|
|
664
|
-
children: h(config.tag, {
|
|
671
|
+
}, props.children),
|
|
672
|
+
children: h(props.config.tag, {
|
|
665
673
|
ref: mergedRef,
|
|
666
|
-
...htmlProps
|
|
667
|
-
}, children)
|
|
674
|
+
...props.htmlProps
|
|
675
|
+
}, props.children)
|
|
668
676
|
});
|
|
669
677
|
};
|
|
670
678
|
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["h","applyEnter","applyLeave","applyReducedMotion","isVNode"],"sources":["../src/useAnimationEnd.ts","../src/useReducedMotion.ts","../../../core/core/lib/jsx-runtime.js","../src/kinetic/CollapseRenderer.tsx","../src/useTransitionState.ts","../src/utils.ts","../src/kinetic/TransitionItem.tsx","../src/kinetic/GroupRenderer.tsx","../src/kinetic/StaggerRenderer.tsx","../src/kinetic/TransitionRenderer.tsx","../src/kinetic/createKineticComponent.tsx","../src/kinetic.ts","../src/presets.ts"],"sourcesContent":["import type { Ref } from '@pyreon/core'\nimport { watch } from '@pyreon/reactivity'\n\nconst DEFAULT_TIMEOUT = 5000\n\nexport type UseAnimationEnd = (options: {\n ref: Ref<HTMLElement>\n onEnd: () => void\n active: () => boolean\n timeout?: number | undefined\n}) => void\n\nconst useAnimationEnd: UseAnimationEnd = ({ ref, onEnd, active, timeout = DEFAULT_TIMEOUT }) => {\n let called = false\n\n watch(\n active,\n (isActive) => {\n if (!isActive) {\n called = false\n return\n }\n\n const el = ref.current\n if (!el) return\n\n called = false\n\n const done = () => {\n if (called) return\n called = true\n el.removeEventListener('transitionend', handleEnd)\n el.removeEventListener('animationend', handleEnd)\n clearTimeout(timer)\n onEnd()\n }\n\n const handleEnd = (e: Event) => {\n // Ignore bubbled events from children\n if (e.target !== el) return\n done()\n }\n\n el.addEventListener('transitionend', handleEnd)\n el.addEventListener('animationend', handleEnd)\n\n const timer = setTimeout(done, timeout)\n\n return () => {\n el.removeEventListener('transitionend', handleEnd)\n el.removeEventListener('animationend', handleEnd)\n clearTimeout(timer)\n }\n },\n { immediate: true },\n )\n}\n\nexport default useAnimationEnd\n","import { onMount, onUnmount } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\n\n/**\n * Inline reduced-motion check for kinetic package.\n * Avoids depending on @pyreon/hooks for a single media query.\n */\nexport function useReducedMotion(): () => boolean {\n const matches = signal(false)\n let mql: MediaQueryList | undefined\n\n const onChange = (e: MediaQueryListEvent) => {\n matches.set(e.matches)\n }\n\n onMount(() => {\n mql = window.matchMedia('(prefers-reduced-motion: reduce)')\n matches.set(mql.matches)\n mql.addEventListener('change', onChange)\n return undefined\n })\n\n onUnmount(() => {\n mql?.removeEventListener('change', onChange)\n })\n\n return matches\n}\n","//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNode } from '@pyreon/core'\nimport { createRef, h, Show } from '@pyreon/core'\nimport { runUntracked, signal, watch } from '@pyreon/reactivity'\nimport type { CSSProperties, TransitionCallbacks, TransitionStage } from '../types'\nimport useAnimationEnd from '../useAnimationEnd'\nimport { useReducedMotion } from '../useReducedMotion'\nimport type { KineticConfig } from './types'\n\ntype CollapseRendererProps = {\n config: KineticConfig\n htmlProps: Record<string, unknown>\n show: () => boolean\n appear?: boolean | undefined\n timeout?: number | undefined\n transition?: string | undefined\n callbacks: Partial<TransitionCallbacks>\n children: VNode | VNode[]\n}\n\n/**\n * Renders a height-animated collapse. The config.tag becomes the outer\n * wrapper (overflow:hidden + animated height). An inner div measures\n * scrollHeight for the target value.\n */\nconst CollapseRenderer = ({\n config,\n htmlProps,\n show,\n appear,\n timeout,\n transition,\n callbacks,\n children,\n}: CollapseRendererProps): VNode | null => {\n const reducedMotion = useReducedMotion()\n let wrapperRef: { current: HTMLElement | null } = createRef<HTMLElement>()\n const contentRef = createRef<HTMLDivElement>()\n\n const effectiveAppear = appear ?? config.appear ?? false\n const effectiveTimeout = timeout ?? config.timeout ?? 5000\n const effectiveTransition = transition ?? config.transition ?? 'height 300ms ease'\n\n const initialShow = show()\n const needsAppear = effectiveAppear && initialShow\n const stage = signal<TransitionStage>(initialShow ? 'entered' : 'hidden')\n let isInitialMount = true\n let appearTriggered = false\n\n // Intercept ref assignment to trigger appear after all refs are wired\n if (needsAppear) {\n const orig = wrapperRef\n const proxy = { current: null as HTMLElement | null }\n Object.defineProperty(proxy, 'current', {\n get() {\n return orig.current\n },\n set(node: HTMLElement | null) {\n orig.current = node\n if (node && !appearTriggered) {\n appearTriggered = true\n queueMicrotask(() => stage.set('entering'))\n }\n },\n })\n wrapperRef = proxy\n }\n\n // State machine transitions\n watch(\n show,\n (showVal) => {\n if (isInitialMount) {\n isInitialMount = false\n // appear case is handled by ref proxy above\n return\n }\n\n const currentStage = runUntracked(() => stage())\n if (showVal && (currentStage === 'hidden' || currentStage === 'leaving')) {\n stage.set('entering')\n } else if (!showVal && (currentStage === 'entered' || currentStage === 'entering')) {\n stage.set('leaving')\n }\n },\n { immediate: true },\n )\n\n // Animate height\n watch(\n () => stage(),\n (currentStage) => {\n const wrapper = wrapperRef.current\n const content = contentRef.current\n if (!wrapper || !content) return\n\n if (reducedMotion()) {\n if (currentStage === 'entering') {\n callbacks.onEnter?.()\n wrapper.style.height = 'auto'\n wrapper.style.overflow = ''\n callbacks.onAfterEnter?.()\n stage.set('entered')\n } else if (currentStage === 'leaving') {\n callbacks.onLeave?.()\n wrapper.style.height = '0px'\n wrapper.style.overflow = 'hidden'\n callbacks.onAfterLeave?.()\n stage.set('hidden')\n }\n return\n }\n\n if (currentStage === 'entering') {\n callbacks.onEnter?.()\n const height = content.scrollHeight\n wrapper.style.transition = 'none'\n wrapper.style.height = '0px'\n wrapper.style.overflow = 'hidden'\n // Force reflow\n void wrapper.offsetHeight\n wrapper.style.transition = effectiveTransition\n wrapper.style.height = `${height}px`\n }\n\n if (currentStage === 'leaving') {\n callbacks.onLeave?.()\n const height = content.scrollHeight\n wrapper.style.transition = 'none'\n wrapper.style.height = `${height}px`\n wrapper.style.overflow = 'hidden'\n // Force reflow\n void wrapper.offsetHeight\n wrapper.style.transition = effectiveTransition\n wrapper.style.height = '0px'\n }\n },\n { immediate: true },\n )\n\n useAnimationEnd({\n ref: wrapperRef,\n active: () => (stage() === 'entering' || stage() === 'leaving') && !reducedMotion(),\n timeout: effectiveTimeout,\n onEnd: () => {\n const wrapper = wrapperRef.current\n if (stage() === 'entering') {\n if (wrapper) {\n wrapper.style.height = 'auto'\n wrapper.style.overflow = ''\n wrapper.style.transition = ''\n }\n callbacks.onAfterEnter?.()\n stage.set('entered')\n } else if (stage() === 'leaving') {\n callbacks.onAfterLeave?.()\n stage.set('hidden')\n }\n },\n })\n\n const shouldRender = () => stage() !== 'hidden'\n\n const wrapperStyle: CSSProperties = {\n ...((htmlProps.style as CSSProperties) ?? {}),\n ...(stage() !== 'entered' ? { overflow: 'hidden' } : {}),\n ...(stage() === 'hidden' ? { height: '0px' } : stage() === 'entered' ? { height: 'auto' } : {}),\n }\n\n return h(\n config.tag,\n { ref: wrapperRef, ...htmlProps, style: wrapperStyle },\n <Show when={shouldRender}>\n <div ref={contentRef}>{children}</div>\n </Show>,\n )\n}\n\nexport default CollapseRenderer\n","import { createRef } from '@pyreon/core'\nimport { runUntracked, signal, watch } from '@pyreon/reactivity'\nimport type { TransitionStage, TransitionStateResult } from './types'\n\nexport type UseTransitionState = (options: {\n show: () => boolean\n appear?: boolean | undefined\n}) => TransitionStateResult\n\nconst useTransitionState: UseTransitionState = ({ show, appear = false }) => {\n const initialShow = show()\n // When appear=true and show starts true, mount the element (stage='entered')\n // but defer the enter animation until the ref is connected.\n const needsAppear = appear && initialShow\n const stage = signal<TransitionStage>(initialShow ? 'entered' : 'hidden')\n const elementRef = createRef<HTMLElement>()\n let isInitialMount = true\n let appearTriggered = false\n\n // Ref callback that triggers the appear animation once the element is wired\n const refCallback = (node: HTMLElement | null) => {\n elementRef.current = node\n if (node && needsAppear && !appearTriggered) {\n appearTriggered = true\n stage.set('entering')\n }\n }\n\n watch(\n show,\n (showVal) => {\n if (isInitialMount) {\n isInitialMount = false\n // appear case is handled by refCallback above\n return\n }\n\n const currentStage = runUntracked(() => stage())\n if (showVal && (currentStage === 'hidden' || currentStage === 'leaving')) {\n stage.set('entering')\n } else if (!showVal && (currentStage === 'entered' || currentStage === 'entering')) {\n stage.set('leaving')\n }\n },\n { immediate: true },\n )\n\n const complete = () => {\n const current = stage()\n if (current === 'entering') stage.set('entered')\n if (current === 'leaving') stage.set('hidden')\n }\n\n return {\n stage,\n ref: refCallback,\n shouldMount: () => stage() !== 'hidden',\n complete,\n }\n}\n\nexport default useTransitionState\n","import type { Ref, VNode } from '@pyreon/core'\nimport type { CSSProperties } from './types'\n\nconst splitCache = new Map<string, string[]>()\nconst splitClasses = (classes: string): string[] => {\n let cached = splitCache.get(classes)\n if (!cached) {\n cached = classes.split(/\\s+/).filter(Boolean)\n splitCache.set(classes, cached)\n }\n return cached\n}\n\n/** Adds space-separated CSS classes to an element. */\nexport const addClasses = (el: HTMLElement, classes: string | undefined) => {\n if (!classes) return\n const list = splitClasses(classes)\n if (list.length > 0) el.classList.add(...list)\n}\n\n/** Removes space-separated CSS classes from an element. */\nexport const removeClasses = (el: HTMLElement, classes: string | undefined) => {\n if (!classes) return\n const list = splitClasses(classes)\n if (list.length > 0) el.classList.remove(...list)\n}\n\n/**\n * Executes callback after two animation frames (double-rAF).\n * Ensures the browser paints the current state before applying changes,\n * which is required for CSS transitions to trigger.\n */\nexport const nextFrame = (callback: () => void): number =>\n requestAnimationFrame(() => {\n requestAnimationFrame(callback)\n })\n\n/** Merges two className strings, filtering undefined/empty. */\nexport const mergeClassNames = (\n existing: string | undefined,\n additional: string | undefined,\n): string | undefined => {\n const parts = [existing, additional].filter(Boolean)\n return parts.length > 0 ? parts.join(' ') : undefined\n}\n\n/** Merges two CSSProperties objects, with `b` taking precedence. */\nexport const mergeStyles = (\n a: CSSProperties | undefined,\n b: CSSProperties | undefined,\n): CSSProperties | undefined => {\n if (!a && !b) return undefined\n if (!a) return b\n if (!b) return a\n return { ...a, ...b }\n}\n\n// ─── Ref & Motion Utilities ─────────────────────────────────\n\ntype RefCallback<T> = (node: T | null) => void\ntype RefLike<T> = RefCallback<T> | Ref<T>\n\n/** Merges multiple refs (callback or object) into a single callback ref. */\nexport const mergeRefs = <T>(...refs: (RefLike<T> | undefined)[]): ((node: T | null) => void) => {\n return (node: T | null) => {\n for (const ref of refs) {\n if (!ref) continue\n if (typeof ref === 'function') {\n ref(node)\n } else {\n ;(ref as { current: unknown }).current = node\n }\n }\n }\n}\n\n/** Clones a VNode with merged props. */\nexport const cloneVNode = (vnode: VNode, extraProps: Record<string, unknown>): VNode => ({\n ...vnode,\n props: { ...vnode.props, ...extraProps },\n})\n","import type { VNode } from '@pyreon/core'\nimport { createRef, Show } from '@pyreon/core'\nimport { watch } from '@pyreon/reactivity'\nimport type { ClassTransitionProps, StyleTransitionProps, TransitionCallbacks } from '../types'\nimport useAnimationEnd from '../useAnimationEnd'\nimport { useReducedMotion } from '../useReducedMotion'\nimport useTransitionState from '../useTransitionState'\nimport { addClasses, cloneVNode, mergeRefs, mergeStyles, nextFrame, removeClasses } from '../utils'\n\ntype TransitionItemProps = ClassTransitionProps &\n StyleTransitionProps &\n TransitionCallbacks & {\n show: () => boolean\n appear?: boolean | undefined\n unmount?: boolean | undefined\n timeout?: number | undefined\n delay?: number | undefined\n children: VNode\n }\n\nconst applyEnter = (el: HTMLElement, config: ClassTransitionProps & StyleTransitionProps) => {\n addClasses(el, config.enter)\n addClasses(el, config.enterFrom)\n if (config.enterStyle) Object.assign(el.style, config.enterStyle)\n if (config.enterTransition) el.style.transition = config.enterTransition\n\n return nextFrame(() => {\n removeClasses(el, config.enterFrom)\n addClasses(el, config.enterTo)\n if (config.enterToStyle) Object.assign(el.style, config.enterToStyle)\n })\n}\n\nconst applyLeave = (el: HTMLElement, config: ClassTransitionProps & StyleTransitionProps) => {\n removeClasses(el, config.enter)\n removeClasses(el, config.enterTo)\n\n addClasses(el, config.leave)\n addClasses(el, config.leaveFrom)\n if (config.leaveStyle) Object.assign(el.style, config.leaveStyle)\n if (config.leaveTransition) el.style.transition = config.leaveTransition\n\n return nextFrame(() => {\n removeClasses(el, config.leaveFrom)\n addClasses(el, config.leaveTo)\n if (config.leaveToStyle) Object.assign(el.style, config.leaveToStyle)\n })\n}\n\nconst applyReducedMotion = (\n stage: string,\n callbacks: Partial<TransitionCallbacks>,\n complete: () => void,\n) => {\n if (stage === 'entering') {\n callbacks.onEnter?.()\n callbacks.onAfterEnter?.()\n complete()\n } else if (stage === 'leaving') {\n callbacks.onLeave?.()\n callbacks.onAfterLeave?.()\n complete()\n }\n}\n\n/**\n * Internal per-child transition component. Used by StaggerRenderer and\n * GroupRenderer to give each child its own animation state.\n *\n * Uses cloneVNode to inject ref onto the child — the child must accept ref.\n */\nconst TransitionItem = ({\n show,\n appear = false,\n unmount = true,\n timeout = 5000,\n enter,\n enterFrom,\n enterTo,\n leave,\n leaveFrom,\n leaveTo,\n enterStyle,\n enterToStyle,\n enterTransition,\n leaveStyle,\n leaveToStyle,\n leaveTransition,\n onEnter,\n onAfterEnter,\n onLeave,\n onAfterLeave,\n children,\n}: TransitionItemProps): VNode | null => {\n const reducedMotion = useReducedMotion()\n const { stage, ref: stateRef, shouldMount, complete } = useTransitionState({ show, appear })\n\n const elementRef = createRef<HTMLElement>()\n const mergedRef = mergeRefs(\n elementRef,\n stateRef,\n (children.props as Record<string, unknown>)?.ref as\n | ((el: HTMLElement | null) => void)\n | undefined,\n )\n\n const callbacks = {\n onEnter,\n onAfterEnter,\n onLeave,\n onAfterLeave,\n }\n\n const transitionConfig = {\n enter,\n enterFrom,\n enterTo,\n leave,\n leaveFrom,\n leaveTo,\n enterStyle,\n enterToStyle,\n enterTransition,\n leaveStyle,\n leaveToStyle,\n leaveTransition,\n }\n\n useAnimationEnd({\n ref: elementRef,\n active: () => (stage() === 'entering' || stage() === 'leaving') && !reducedMotion(),\n timeout,\n onEnd: () => {\n if (stage() === 'entering') {\n callbacks.onAfterEnter?.()\n } else if (stage() === 'leaving') {\n callbacks.onAfterLeave?.()\n }\n complete()\n },\n })\n\n watch(\n () => stage(),\n (currentStage) => {\n const el = elementRef.current\n if (!el) return\n\n if (reducedMotion()) {\n applyReducedMotion(currentStage, callbacks, complete)\n return\n }\n\n if (currentStage === 'entering') {\n callbacks.onEnter?.()\n const frameId = applyEnter(el, transitionConfig)\n return () => cancelAnimationFrame(frameId)\n }\n\n if (currentStage === 'leaving') {\n callbacks.onLeave?.()\n const frameId = applyLeave(el, transitionConfig)\n return () => cancelAnimationFrame(frameId)\n }\n\n if (currentStage === 'entered') {\n removeClasses(el, enter)\n el.style.transition = ''\n }\n },\n { immediate: true },\n )\n\n return (\n <Show\n when={shouldMount}\n fallback={\n unmount\n ? null\n : cloneVNode(children, {\n ref: mergedRef,\n style: mergeStyles(\n (children.props as Record<string, unknown>)?.style as\n | Record<string, string | number | undefined>\n | undefined,\n { display: 'none' },\n ),\n })\n }\n >\n {cloneVNode(children, { ref: mergedRef })}\n </Show>\n )\n}\n\nexport default TransitionItem\n","import type { VNode } from '@pyreon/core'\nimport { h } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\nimport type { TransitionCallbacks } from '../types'\nimport TransitionItem from './TransitionItem'\nimport type { KineticConfig } from './types'\n\ntype GroupRendererProps = {\n config: KineticConfig\n htmlProps: Record<string, unknown>\n appear?: boolean | undefined\n timeout?: number | undefined\n callbacks: Partial<TransitionCallbacks>\n /**\n * Children can be a static array OR a reactive accessor `() => VNode[]`.\n * When passed as an accessor, GroupRenderer tracks changes and\n * animates entering/leaving children automatically.\n */\n children: VNode[] | (() => VNode[])\n}\n\ntype KeyedChild = { key: string | number; element: VNode }\n\nconst isVNode = (child: unknown): child is VNode =>\n child != null && typeof child === 'object' && 'type' in (child as object)\n\nconst getKeyedChildren = (children: VNode[]): KeyedChild[] => {\n const result: KeyedChild[] = []\n for (const child of children) {\n if (isVNode(child)) {\n const key = (child as VNode & { key?: string | number }).key\n if (key != null) {\n result.push({ key, element: child })\n }\n }\n }\n return result\n}\n\n/**\n * Renders children with key-based enter/exit animation (no `show` prop).\n * Children that appear (new key) animate in. Children that disappear\n * (removed key) stay in DOM during leave animation, then unmount.\n * config.tag wraps all children as a container element.\n *\n * In Pyreon, components run once. Pass children as a reactive accessor\n * `() => VNode[]` for the group to detect changes and animate entries/exits.\n */\nconst GroupRenderer = ({\n config,\n htmlProps,\n appear,\n timeout,\n callbacks,\n children,\n}: GroupRendererProps): VNode | null => {\n const effectiveAppear = appear ?? config.appear ?? false\n const effectiveTimeout = timeout ?? config.timeout ?? 5000\n\n const prevMap = new Map<string | number, VNode>()\n const leavingMap = new Map<string | number, VNode>()\n const forceUpdate = signal(0)\n\n // Normalize children to an accessor\n const getChildren = typeof children === 'function' ? (children as () => VNode[]) : () => children\n\n // Track initial keys for appear animation logic\n const initialKeyed = getKeyedChildren(getChildren())\n const initialKeys = new Set(initialKeyed.map((c) => c.key))\n for (const { key, element } of initialKeyed) {\n prevMap.set(key, element)\n }\n\n const handleAfterLeave = (key: string | number) => {\n leavingMap.delete(key)\n callbacks.onAfterLeave?.()\n forceUpdate.update((c) => c + 1)\n }\n\n // Reactive accessor — re-evaluates when children() or forceUpdate changes\n return (() => {\n forceUpdate()\n\n const currentChildren = getChildren()\n const currentKeyed = getKeyedChildren(currentChildren)\n const currentMap = new Map<string | number, VNode>()\n for (const { key, element } of currentKeyed) {\n currentMap.set(key, element)\n }\n\n // Detect leaving children\n for (const [key, child] of prevMap) {\n if (!currentMap.has(key) && !leavingMap.has(key)) {\n leavingMap.set(key, child)\n }\n }\n\n // Cancel leave if child reappears\n for (const key of currentMap.keys()) {\n leavingMap.delete(key)\n }\n\n // Update prev for next diff\n prevMap.clear()\n for (const [key, element] of currentMap) {\n prevMap.set(key, element)\n }\n\n // Merge current + leaving\n const allEntries: KeyedChild[] = [...currentKeyed]\n for (const [key, element] of leavingMap) {\n allEntries.push({ key, element })\n }\n\n const groupedChildren = allEntries.map(({ key, element }) => {\n const isInitial = initialKeys.has(key)\n const isShowing = currentMap.has(key)\n\n return (\n <TransitionItem\n show={() => isShowing}\n appear={isInitial ? effectiveAppear : true}\n timeout={effectiveTimeout}\n enterStyle={config.enterStyle}\n enterToStyle={config.enterToStyle}\n enterTransition={config.enterTransition}\n leaveStyle={config.leaveStyle}\n leaveToStyle={config.leaveToStyle}\n leaveTransition={config.leaveTransition}\n enter={config.enter}\n enterFrom={config.enterFrom}\n enterTo={config.enterTo}\n leave={config.leave}\n leaveFrom={config.leaveFrom}\n leaveTo={config.leaveTo}\n onAfterLeave={() => handleAfterLeave(key)}\n >\n {element}\n </TransitionItem>\n )\n })\n\n return h(config.tag, { ...htmlProps }, ...groupedChildren)\n }) as unknown as VNode\n}\n\nexport default GroupRenderer\n","import type { VNode } from '@pyreon/core'\nimport { h } from '@pyreon/core'\nimport type { CSSProperties, TransitionCallbacks } from '../types'\nimport { cloneVNode } from '../utils'\nimport TransitionItem from './TransitionItem'\nimport type { KineticConfig } from './types'\n\ntype StaggerRendererProps = {\n config: KineticConfig\n htmlProps: Record<string, unknown>\n show: () => boolean\n appear?: boolean | undefined\n timeout?: number | undefined\n interval?: number | undefined\n reverseLeave?: boolean | undefined\n callbacks: Partial<TransitionCallbacks>\n children: VNode[]\n}\n\nconst isVNode = (child: unknown): child is VNode =>\n child != null && typeof child === 'object' && 'type' in (child as object)\n\n/**\n * Renders children with staggered enter/exit animation.\n * config.tag wraps the staggered children as a container element.\n * Each child is individually animated via TransitionItem.\n */\nconst StaggerRenderer = ({\n config,\n htmlProps,\n show,\n appear,\n timeout,\n interval,\n reverseLeave,\n callbacks,\n children,\n}: StaggerRendererProps): VNode | null => {\n const effectiveAppear = appear ?? config.appear ?? false\n const effectiveTimeout = timeout ?? config.timeout ?? 5000\n const effectiveInterval = interval ?? config.interval ?? 50\n const effectiveReverseLeave = reverseLeave ?? config.reverseLeave ?? false\n\n const childArray = (Array.isArray(children) ? children : [children]).filter(isVNode)\n const count = childArray.length\n\n const staggeredChildren = childArray.map((child, index) => {\n const staggerIndex = !show() && effectiveReverseLeave ? count - 1 - index : index\n const delay = staggerIndex * effectiveInterval\n\n return (\n <TransitionItem\n key={(child as VNode & { key?: string | number }).key ?? index}\n show={show}\n appear={effectiveAppear}\n timeout={effectiveTimeout + delay}\n enterStyle={config.enterStyle}\n enterToStyle={config.enterToStyle}\n enterTransition={config.enterTransition}\n leaveStyle={config.leaveStyle}\n leaveToStyle={config.leaveToStyle}\n leaveTransition={config.leaveTransition}\n enter={config.enter}\n enterFrom={config.enterFrom}\n enterTo={config.enterTo}\n leave={config.leave}\n leaveFrom={config.leaveFrom}\n leaveTo={config.leaveTo}\n onAfterLeave={\n index === (effectiveReverseLeave ? 0 : count - 1) ? callbacks.onAfterLeave : undefined\n }\n >\n {cloneVNode(child, {\n style: {\n ...((child.props as Record<string, unknown>)?.style as CSSProperties | undefined),\n '--stagger-index': staggerIndex,\n '--stagger-interval': `${effectiveInterval}ms`,\n transitionDelay: `${delay}ms`,\n } as CSSProperties,\n })}\n </TransitionItem>\n )\n })\n\n return h(config.tag, { ...htmlProps }, ...staggeredChildren)\n}\n\nexport default StaggerRenderer\n","import type { VNode } from '@pyreon/core'\nimport { createRef, h, Show } from '@pyreon/core'\nimport { watch } from '@pyreon/reactivity'\nimport type { CSSProperties, TransitionCallbacks } from '../types'\nimport useAnimationEnd from '../useAnimationEnd'\nimport { useReducedMotion } from '../useReducedMotion'\nimport useTransitionState from '../useTransitionState'\nimport { addClasses, mergeRefs, nextFrame, removeClasses } from '../utils'\nimport type { KineticConfig } from './types'\n\ntype TransitionRendererProps = {\n config: KineticConfig\n htmlProps: Record<string, unknown>\n show: () => boolean\n appear?: boolean | undefined\n unmount?: boolean | undefined\n timeout?: number | undefined\n callbacks: Partial<TransitionCallbacks>\n children: VNode | VNode[]\n}\n\nconst applyEnter = (el: HTMLElement, config: KineticConfig) => {\n addClasses(el, config.enter)\n addClasses(el, config.enterFrom)\n if (config.enterStyle) Object.assign(el.style, config.enterStyle)\n if (config.enterTransition) el.style.transition = config.enterTransition\n\n return nextFrame(() => {\n removeClasses(el, config.enterFrom)\n addClasses(el, config.enterTo)\n if (config.enterToStyle) Object.assign(el.style, config.enterToStyle)\n })\n}\n\nconst applyLeave = (el: HTMLElement, config: KineticConfig) => {\n removeClasses(el, config.enter)\n removeClasses(el, config.enterTo)\n\n addClasses(el, config.leave)\n addClasses(el, config.leaveFrom)\n if (config.leaveStyle) Object.assign(el.style, config.leaveStyle)\n if (config.leaveTransition) el.style.transition = config.leaveTransition\n\n return nextFrame(() => {\n removeClasses(el, config.leaveFrom)\n addClasses(el, config.leaveTo)\n if (config.leaveToStyle) Object.assign(el.style, config.leaveToStyle)\n })\n}\n\nconst applyReducedMotion = (\n stage: string,\n cbs: Partial<TransitionCallbacks>,\n complete: () => void,\n) => {\n if (stage === 'entering') {\n cbs.onEnter?.()\n cbs.onAfterEnter?.()\n complete()\n } else if (stage === 'leaving') {\n cbs.onLeave?.()\n cbs.onAfterLeave?.()\n complete()\n }\n}\n\n/**\n * Renders a single element with CSS transition enter/exit animation.\n * Uses h(config.tag) — no cloneElement needed.\n */\nconst TransitionRenderer = ({\n config,\n htmlProps,\n show,\n appear,\n unmount,\n timeout,\n callbacks,\n children,\n}: TransitionRendererProps): VNode | null => {\n const reducedMotion = useReducedMotion()\n const {\n stage,\n ref: stateRef,\n shouldMount,\n complete,\n } = useTransitionState({\n show,\n appear: appear ?? config.appear ?? false,\n })\n\n const elementRef = createRef<HTMLElement>()\n const mergedRef = mergeRefs(elementRef, stateRef)\n\n const effectiveUnmount = unmount ?? config.unmount ?? true\n const effectiveTimeout = timeout ?? config.timeout ?? 5000\n\n useAnimationEnd({\n ref: elementRef,\n active: () => (stage() === 'entering' || stage() === 'leaving') && !reducedMotion(),\n timeout: effectiveTimeout,\n onEnd: () => {\n if (stage() === 'entering') {\n callbacks.onAfterEnter?.()\n } else if (stage() === 'leaving') {\n callbacks.onAfterLeave?.()\n }\n complete()\n },\n })\n\n watch(\n () => stage(),\n (currentStage) => {\n const el = elementRef.current\n if (!el) return\n\n if (reducedMotion()) {\n applyReducedMotion(currentStage, callbacks, complete)\n return\n }\n\n if (currentStage === 'entering') {\n callbacks.onEnter?.()\n const frameId = applyEnter(el, config)\n return () => cancelAnimationFrame(frameId)\n }\n\n if (currentStage === 'leaving') {\n callbacks.onLeave?.()\n const frameId = applyLeave(el, config)\n return () => cancelAnimationFrame(frameId)\n }\n\n if (currentStage === 'entered') {\n removeClasses(el, config.enter)\n el.style.transition = ''\n }\n },\n { immediate: true },\n )\n\n return (\n <Show\n when={shouldMount}\n fallback={\n effectiveUnmount\n ? null\n : h(\n config.tag,\n {\n ref: mergedRef,\n ...htmlProps,\n style: {\n ...((htmlProps.style as CSSProperties) ?? {}),\n display: 'none',\n },\n },\n children,\n )\n }\n >\n {h(config.tag, { ref: mergedRef, ...htmlProps }, children)}\n </Show>\n )\n}\n\nexport default TransitionRenderer\n","import type { VNode } from '@pyreon/core'\nimport type { CSSProperties, TransitionCallbacks } from '../types'\nimport CollapseRenderer from './CollapseRenderer'\nimport GroupRenderer from './GroupRenderer'\nimport StaggerRenderer from './StaggerRenderer'\nimport TransitionRenderer from './TransitionRenderer'\nimport type { ClassConfig, KineticComponent, KineticConfig, KineticMode } from './types'\n\n/** Keys that are kinetic-specific and should not be forwarded as HTML attrs. */\nconst KINETIC_KEYS = new Set([\n 'show',\n 'appear',\n 'unmount',\n 'timeout',\n 'transition',\n 'interval',\n 'reverseLeave',\n 'onEnter',\n 'onAfterEnter',\n 'onLeave',\n 'onAfterLeave',\n])\n\n/**\n * Core factory. Creates a component that delegates to the appropriate\n * renderer based on config.mode, then attaches immutable chain methods\n * via Object.assign.\n */\nconst createKineticComponent = <Tag extends string, Mode extends KineticMode = 'transition'>(\n config: KineticConfig,\n): KineticComponent<Tag, Mode> => {\n const Component = (props: Record<string, unknown>): VNode | null => {\n // Separate kinetic-specific props from HTML pass-through props\n const htmlProps: Record<string, unknown> = {}\n const kineticProps: Record<string, unknown> = {}\n\n for (const key in props) {\n if (KINETIC_KEYS.has(key)) {\n kineticProps[key] = props[key]\n } else {\n htmlProps[key] = props[key]\n }\n }\n\n const {\n show,\n appear,\n unmount,\n timeout,\n transition,\n interval,\n reverseLeave,\n onEnter,\n onAfterEnter,\n onLeave,\n onAfterLeave,\n } = kineticProps as {\n show?: () => boolean\n appear?: boolean\n unmount?: boolean\n timeout?: number\n transition?: string\n interval?: number\n reverseLeave?: boolean\n } & Partial<TransitionCallbacks>\n\n const callbacks: Partial<TransitionCallbacks> = {\n onEnter: onEnter ?? config.onEnter,\n onAfterEnter: onAfterEnter ?? config.onAfterEnter,\n onLeave: onLeave ?? config.onLeave,\n onAfterLeave: onAfterLeave ?? config.onAfterLeave,\n }\n\n // Extract children from htmlProps (it's not an HTML attribute)\n const { children, ...restHtml } = htmlProps\n\n if (config.mode === 'collapse') {\n return (\n <CollapseRenderer\n config={config}\n htmlProps={restHtml}\n show={show as () => boolean}\n appear={appear}\n timeout={timeout}\n transition={transition}\n callbacks={callbacks}\n >\n {children as VNode | VNode[]}\n </CollapseRenderer>\n )\n }\n\n if (config.mode === 'stagger') {\n return (\n <StaggerRenderer\n config={config}\n htmlProps={restHtml}\n show={show as () => boolean}\n appear={appear}\n timeout={timeout}\n interval={interval}\n reverseLeave={reverseLeave}\n callbacks={callbacks}\n >\n {children as VNode[]}\n </StaggerRenderer>\n )\n }\n\n if (config.mode === 'group') {\n return (\n <GroupRenderer\n config={config}\n htmlProps={restHtml}\n appear={appear}\n timeout={timeout}\n callbacks={callbacks}\n >\n {children as VNode[]}\n </GroupRenderer>\n )\n }\n\n // Default: transition mode\n return (\n <TransitionRenderer\n config={config}\n htmlProps={restHtml}\n show={show as () => boolean}\n appear={appear}\n unmount={unmount}\n timeout={timeout}\n callbacks={callbacks}\n >\n {children as VNode | VNode[]}\n </TransitionRenderer>\n )\n }\n\n Component.displayName = `kinetic(${config.tag})`\n\n // Immutable chain methods — each returns a new component with merged config.\n return Object.assign(Component, {\n preset: (preset: Record<string, unknown>) =>\n createKineticComponent<Tag, Mode>({\n ...config,\n ...preset,\n } as KineticConfig),\n\n enter: (styles: CSSProperties) =>\n createKineticComponent<Tag, Mode>({ ...config, enterStyle: styles }),\n\n enterTo: (styles: CSSProperties) =>\n createKineticComponent<Tag, Mode>({ ...config, enterToStyle: styles }),\n\n enterTransition: (value: string) =>\n createKineticComponent<Tag, Mode>({ ...config, enterTransition: value }),\n\n leave: (styles: CSSProperties) =>\n createKineticComponent<Tag, Mode>({ ...config, leaveStyle: styles }),\n\n leaveTo: (styles: CSSProperties) =>\n createKineticComponent<Tag, Mode>({ ...config, leaveToStyle: styles }),\n\n leaveTransition: (value: string) =>\n createKineticComponent<Tag, Mode>({ ...config, leaveTransition: value }),\n\n enterClass: ({ active, from, to }: ClassConfig) =>\n createKineticComponent<Tag, Mode>({\n ...config,\n enter: active,\n enterFrom: from,\n enterTo: to,\n }),\n\n leaveClass: ({ active, from, to }: ClassConfig) =>\n createKineticComponent<Tag, Mode>({\n ...config,\n leave: active,\n leaveFrom: from,\n leaveTo: to,\n }),\n\n config: (opts: Record<string, unknown>) =>\n createKineticComponent<Tag, Mode>({\n ...config,\n ...opts,\n } as KineticConfig),\n\n on: (cbs: Partial<TransitionCallbacks>) =>\n createKineticComponent<Tag, Mode>({ ...config, ...cbs }),\n\n collapse: (opts?: { transition?: string }) =>\n createKineticComponent<Tag, 'collapse'>({\n ...config,\n mode: 'collapse',\n ...opts,\n }),\n\n stagger: (opts?: { interval?: number; reverseLeave?: boolean }) =>\n createKineticComponent<Tag, 'stagger'>({\n ...config,\n mode: 'stagger',\n ...opts,\n }),\n\n group: () => createKineticComponent<Tag, 'group'>({ ...config, mode: 'group' }),\n }) as unknown as KineticComponent<Tag, Mode>\n}\n\nexport default createKineticComponent\n","import createKineticComponent from './kinetic/createKineticComponent'\nimport type { KineticComponent } from './kinetic/types'\n\n/**\n * Creates a reusable animated component via immutable chaining.\n *\n * @example\n * ```tsx\n * // Transition (default)\n * const FadeDiv = kinetic('div').preset(fade)\n *\n * // Collapse\n * const Accordion = kinetic('div').collapse()\n *\n * // Stagger\n * const StaggerList = kinetic('ul').preset(slideUp).stagger({ interval: 50 })\n *\n * // Group (key-based enter/exit)\n * const AnimatedList = kinetic('ul').preset(fade).group()\n * ```\n */\nconst kinetic = <Tag extends string>(tag: Tag): KineticComponent<Tag, 'transition'> =>\n createKineticComponent<Tag, 'transition'>({ tag, mode: 'transition' })\n\nexport default kinetic\n","import type { ClassTransitionProps, StyleTransitionProps } from './types'\n\nexport type Preset = StyleTransitionProps & ClassTransitionProps\n\nexport const fade: Preset = {\n enterStyle: { opacity: 0 },\n enterToStyle: { opacity: 1 },\n enterTransition: 'opacity 300ms ease-out',\n leaveStyle: { opacity: 1 },\n leaveToStyle: { opacity: 0 },\n leaveTransition: 'opacity 200ms ease-in',\n}\n\nexport const scaleIn: Preset = {\n enterStyle: { opacity: 0, transform: 'scale(0.95)' },\n enterToStyle: { opacity: 1, transform: 'scale(1)' },\n enterTransition: 'opacity 300ms ease-out, transform 300ms ease-out',\n leaveStyle: { opacity: 1, transform: 'scale(1)' },\n leaveToStyle: { opacity: 0, transform: 'scale(0.95)' },\n leaveTransition: 'opacity 200ms ease-in, transform 200ms ease-in',\n}\n\nexport const slideUp: Preset = {\n enterStyle: { opacity: 0, transform: 'translateY(16px)' },\n enterToStyle: { opacity: 1, transform: 'translateY(0)' },\n enterTransition: 'opacity 300ms ease-out, transform 300ms ease-out',\n leaveStyle: { opacity: 1, transform: 'translateY(0)' },\n leaveToStyle: { opacity: 0, transform: 'translateY(16px)' },\n leaveTransition: 'opacity 200ms ease-in, transform 200ms ease-in',\n}\n\nexport const slideDown: Preset = {\n enterStyle: { opacity: 0, transform: 'translateY(-16px)' },\n enterToStyle: { opacity: 1, transform: 'translateY(0)' },\n enterTransition: 'opacity 300ms ease-out, transform 300ms ease-out',\n leaveStyle: { opacity: 1, transform: 'translateY(0)' },\n leaveToStyle: { opacity: 0, transform: 'translateY(-16px)' },\n leaveTransition: 'opacity 200ms ease-in, transform 200ms ease-in',\n}\n\nexport const slideLeft: Preset = {\n enterStyle: { opacity: 0, transform: 'translateX(16px)' },\n enterToStyle: { opacity: 1, transform: 'translateX(0)' },\n enterTransition: 'opacity 300ms ease-out, transform 300ms ease-out',\n leaveStyle: { opacity: 1, transform: 'translateX(0)' },\n leaveToStyle: { opacity: 0, transform: 'translateX(16px)' },\n leaveTransition: 'opacity 200ms ease-in, transform 200ms ease-in',\n}\n\nexport const slideRight: Preset = {\n enterStyle: { opacity: 0, transform: 'translateX(-16px)' },\n enterToStyle: { opacity: 1, transform: 'translateX(0)' },\n enterTransition: 'opacity 300ms ease-out, transform 300ms ease-out',\n leaveStyle: { opacity: 1, transform: 'translateX(0)' },\n leaveToStyle: { opacity: 0, transform: 'translateX(-16px)' },\n leaveTransition: 'opacity 200ms ease-in, transform 200ms ease-in',\n}\n\nexport const presets = {\n fade,\n scaleIn,\n slideUp,\n slideDown,\n slideLeft,\n slideRight,\n} as const\n"],"mappings":";;;;AAGA,MAAM,kBAAkB;AASxB,MAAM,mBAAoC,EAAE,KAAK,OAAO,QAAQ,UAAU,sBAAsB;CAC9F,IAAI,SAAS;AAEb,OACE,SACC,aAAa;AACZ,MAAI,CAAC,UAAU;AACb,YAAS;AACT;;EAGF,MAAM,KAAK,IAAI;AACf,MAAI,CAAC,GAAI;AAET,WAAS;EAET,MAAM,aAAa;AACjB,OAAI,OAAQ;AACZ,YAAS;AACT,MAAG,oBAAoB,iBAAiB,UAAU;AAClD,MAAG,oBAAoB,gBAAgB,UAAU;AACjD,gBAAa,MAAM;AACnB,UAAO;;EAGT,MAAM,aAAa,MAAa;AAE9B,OAAI,EAAE,WAAW,GAAI;AACrB,SAAM;;AAGR,KAAG,iBAAiB,iBAAiB,UAAU;AAC/C,KAAG,iBAAiB,gBAAgB,UAAU;EAE9C,MAAM,QAAQ,WAAW,MAAM,QAAQ;AAEvC,eAAa;AACX,MAAG,oBAAoB,iBAAiB,UAAU;AAClD,MAAG,oBAAoB,gBAAgB,UAAU;AACjD,gBAAa,MAAM;;IAGvB,EAAE,WAAW,MAAM,CACpB;;;;;;;;;AChDH,SAAgB,mBAAkC;CAChD,MAAM,UAAU,OAAO,MAAM;CAC7B,IAAI;CAEJ,MAAM,YAAY,MAA2B;AAC3C,UAAQ,IAAI,EAAE,QAAQ;;AAGxB,eAAc;AACZ,QAAM,OAAO,WAAW,mCAAmC;AAC3D,UAAQ,IAAI,IAAI,QAAQ;AACxB,MAAI,iBAAiB,UAAU,SAAS;GAExC;AAEF,iBAAgB;AACd,OAAK,oBAAoB,UAAU,SAAS;GAC5C;AAEF,QAAO;;;;;;;;;;;;;ACfT,MAAM,cAAc,EAAE;AACtB,SAASA,IAAE,MAAM,OAAO,GAAG,UAAU;AACpC,QAAO;EACN;EACA,OAAO,SAAS;EAChB,UAAU,kBAAkB,SAAS;EACrC,KAAK,OAAO,OAAO;EACnB;;AAEF,SAAS,kBAAkB,UAAU;AACpC,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IAAK,KAAI,MAAM,QAAQ,SAAS,GAAG,CAAE,QAAO,gBAAgB,SAAS;AAC1G,QAAO;;AAER,SAAS,gBAAgB,UAAU;CAClC,MAAM,SAAS,EAAE;AACjB,MAAK,MAAM,SAAS,SAAU,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAK,GAAG,gBAAgB,MAAM,CAAC;KACzF,QAAO,KAAK,MAAM;AACvB,QAAO;;;;;;;;;AAYR,SAAS,IAAI,MAAM,OAAO,KAAK;CAC9B,MAAM,EAAE,UAAU,GAAG,SAAS;CAC9B,MAAM,eAAe,OAAO,OAAO;EAClC,GAAG;EACH;EACA,GAAG;AACJ,KAAI,OAAO,SAAS,WAAY,QAAOA,IAAE,MAAM,aAAa,KAAK,IAAI;EACpE,GAAG;EACH;EACA,GAAG,aAAa;AACjB,QAAOA,IAAE,MAAM,cAAc,GAAG,aAAa,KAAK,IAAI,EAAE,GAAG,MAAM,QAAQ,SAAS,GAAG,WAAW,CAAC,SAAS,CAAC;;;;;;;;;;AC1B5G,MAAM,oBAAoB,EACxB,QACA,WACA,MACA,QACA,SACA,YACA,WACA,eACyC;CACzC,MAAM,gBAAgB,kBAAkB;CACxC,IAAI,aAA8C,WAAwB;CAC1E,MAAM,aAAa,WAA2B;CAE9C,MAAM,kBAAkB,UAAU,OAAO,UAAU;CACnD,MAAM,mBAAmB,WAAW,OAAO,WAAW;CACtD,MAAM,sBAAsB,cAAc,OAAO,cAAc;CAE/D,MAAM,cAAc,MAAM;CAC1B,MAAM,cAAc,mBAAmB;CACvC,MAAM,QAAQ,OAAwB,cAAc,YAAY,SAAS;CACzE,IAAI,iBAAiB;CACrB,IAAI,kBAAkB;AAGtB,KAAI,aAAa;EACf,MAAM,OAAO;EACb,MAAM,QAAQ,EAAE,SAAS,MAA4B;AACrD,SAAO,eAAe,OAAO,WAAW;GACtC,MAAM;AACJ,WAAO,KAAK;;GAEd,IAAI,MAA0B;AAC5B,SAAK,UAAU;AACf,QAAI,QAAQ,CAAC,iBAAiB;AAC5B,uBAAkB;AAClB,0BAAqB,MAAM,IAAI,WAAW,CAAC;;;GAGhD,CAAC;AACF,eAAa;;AAIf,OACE,OACC,YAAY;AACX,MAAI,gBAAgB;AAClB,oBAAiB;AAEjB;;EAGF,MAAM,eAAe,mBAAmB,OAAO,CAAC;AAChD,MAAI,YAAY,iBAAiB,YAAY,iBAAiB,WAC5D,OAAM,IAAI,WAAW;WACZ,CAAC,YAAY,iBAAiB,aAAa,iBAAiB,YACrE,OAAM,IAAI,UAAU;IAGxB,EAAE,WAAW,MAAM,CACpB;AAGD,aACQ,OAAO,GACZ,iBAAiB;EAChB,MAAM,UAAU,WAAW;EAC3B,MAAM,UAAU,WAAW;AAC3B,MAAI,CAAC,WAAW,CAAC,QAAS;AAE1B,MAAI,eAAe,EAAE;AACnB,OAAI,iBAAiB,YAAY;AAC/B,cAAU,WAAW;AACrB,YAAQ,MAAM,SAAS;AACvB,YAAQ,MAAM,WAAW;AACzB,cAAU,gBAAgB;AAC1B,UAAM,IAAI,UAAU;cACX,iBAAiB,WAAW;AACrC,cAAU,WAAW;AACrB,YAAQ,MAAM,SAAS;AACvB,YAAQ,MAAM,WAAW;AACzB,cAAU,gBAAgB;AAC1B,UAAM,IAAI,SAAS;;AAErB;;AAGF,MAAI,iBAAiB,YAAY;AAC/B,aAAU,WAAW;GACrB,MAAM,SAAS,QAAQ;AACvB,WAAQ,MAAM,aAAa;AAC3B,WAAQ,MAAM,SAAS;AACvB,WAAQ,MAAM,WAAW;AAEzB,GAAK,QAAQ;AACb,WAAQ,MAAM,aAAa;AAC3B,WAAQ,MAAM,SAAS,GAAG,OAAO;;AAGnC,MAAI,iBAAiB,WAAW;AAC9B,aAAU,WAAW;GACrB,MAAM,SAAS,QAAQ;AACvB,WAAQ,MAAM,aAAa;AAC3B,WAAQ,MAAM,SAAS,GAAG,OAAO;AACjC,WAAQ,MAAM,WAAW;AAEzB,GAAK,QAAQ;AACb,WAAQ,MAAM,aAAa;AAC3B,WAAQ,MAAM,SAAS;;IAG3B,EAAE,WAAW,MAAM,CACpB;AAED,iBAAgB;EACd,KAAK;EACL,eAAe,OAAO,KAAK,cAAc,OAAO,KAAK,cAAc,CAAC,eAAe;EACnF,SAAS;EACT,aAAa;GACX,MAAM,UAAU,WAAW;AAC3B,OAAI,OAAO,KAAK,YAAY;AAC1B,QAAI,SAAS;AACX,aAAQ,MAAM,SAAS;AACvB,aAAQ,MAAM,WAAW;AACzB,aAAQ,MAAM,aAAa;;AAE7B,cAAU,gBAAgB;AAC1B,UAAM,IAAI,UAAU;cACX,OAAO,KAAK,WAAW;AAChC,cAAU,gBAAgB;AAC1B,UAAM,IAAI,SAAS;;;EAGxB,CAAC;CAEF,MAAM,qBAAqB,OAAO,KAAK;CAEvC,MAAM,eAA8B;EAClC,GAAK,UAAU,SAA2B,EAAE;EAC5C,GAAI,OAAO,KAAK,YAAY,EAAE,UAAU,UAAU,GAAG,EAAE;EACvD,GAAI,OAAO,KAAK,WAAW,EAAE,QAAQ,OAAO,GAAG,OAAO,KAAK,YAAY,EAAE,QAAQ,QAAQ,GAAG,EAAE;EAC/F;AAED,QAAO,EACL,OAAO,KACP;EAAE,KAAK;EAAY,GAAG;EAAW,OAAO;EAAc,EACtD,oBAAC,MAAD;EAAM,MAAM;YACV,oBAAC,OAAD;GAAK,KAAK;GAAa;GAAe;EACjC,EACR;;;;;ACrKH,MAAM,sBAA0C,EAAE,MAAM,SAAS,YAAY;CAC3E,MAAM,cAAc,MAAM;CAG1B,MAAM,cAAc,UAAU;CAC9B,MAAM,QAAQ,OAAwB,cAAc,YAAY,SAAS;CACzE,MAAM,aAAa,WAAwB;CAC3C,IAAI,iBAAiB;CACrB,IAAI,kBAAkB;CAGtB,MAAM,eAAe,SAA6B;AAChD,aAAW,UAAU;AACrB,MAAI,QAAQ,eAAe,CAAC,iBAAiB;AAC3C,qBAAkB;AAClB,SAAM,IAAI,WAAW;;;AAIzB,OACE,OACC,YAAY;AACX,MAAI,gBAAgB;AAClB,oBAAiB;AAEjB;;EAGF,MAAM,eAAe,mBAAmB,OAAO,CAAC;AAChD,MAAI,YAAY,iBAAiB,YAAY,iBAAiB,WAC5D,OAAM,IAAI,WAAW;WACZ,CAAC,YAAY,iBAAiB,aAAa,iBAAiB,YACrE,OAAM,IAAI,UAAU;IAGxB,EAAE,WAAW,MAAM,CACpB;CAED,MAAM,iBAAiB;EACrB,MAAM,UAAU,OAAO;AACvB,MAAI,YAAY,WAAY,OAAM,IAAI,UAAU;AAChD,MAAI,YAAY,UAAW,OAAM,IAAI,SAAS;;AAGhD,QAAO;EACL;EACA,KAAK;EACL,mBAAmB,OAAO,KAAK;EAC/B;EACD;;;;;ACvDH,MAAM,6BAAa,IAAI,KAAuB;AAC9C,MAAM,gBAAgB,YAA8B;CAClD,IAAI,SAAS,WAAW,IAAI,QAAQ;AACpC,KAAI,CAAC,QAAQ;AACX,WAAS,QAAQ,MAAM,MAAM,CAAC,OAAO,QAAQ;AAC7C,aAAW,IAAI,SAAS,OAAO;;AAEjC,QAAO;;;AAIT,MAAa,cAAc,IAAiB,YAAgC;AAC1E,KAAI,CAAC,QAAS;CACd,MAAM,OAAO,aAAa,QAAQ;AAClC,KAAI,KAAK,SAAS,EAAG,IAAG,UAAU,IAAI,GAAG,KAAK;;;AAIhD,MAAa,iBAAiB,IAAiB,YAAgC;AAC7E,KAAI,CAAC,QAAS;CACd,MAAM,OAAO,aAAa,QAAQ;AAClC,KAAI,KAAK,SAAS,EAAG,IAAG,UAAU,OAAO,GAAG,KAAK;;;;;;;AAQnD,MAAa,aAAa,aACxB,4BAA4B;AAC1B,uBAAsB,SAAS;EAC/B;;AAYJ,MAAa,eACX,GACA,MAC8B;AAC9B,KAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,KAAI,CAAC,EAAG,QAAO;AACf,KAAI,CAAC,EAAG,QAAO;AACf,QAAO;EAAE,GAAG;EAAG,GAAG;EAAG;;;AASvB,MAAa,aAAgB,GAAG,SAAiE;AAC/F,SAAQ,SAAmB;AACzB,OAAK,MAAM,OAAO,MAAM;AACtB,OAAI,CAAC,IAAK;AACV,OAAI,OAAO,QAAQ,WACjB,KAAI,KAAK;OAER,CAAC,IAA6B,UAAU;;;;;AAOjD,MAAa,cAAc,OAAc,gBAAgD;CACvF,GAAG;CACH,OAAO;EAAE,GAAG,MAAM;EAAO,GAAG;EAAY;CACzC;;;;AC5DD,MAAMC,gBAAc,IAAiB,WAAwD;AAC3F,YAAW,IAAI,OAAO,MAAM;AAC5B,YAAW,IAAI,OAAO,UAAU;AAChC,KAAI,OAAO,WAAY,QAAO,OAAO,GAAG,OAAO,OAAO,WAAW;AACjE,KAAI,OAAO,gBAAiB,IAAG,MAAM,aAAa,OAAO;AAEzD,QAAO,gBAAgB;AACrB,gBAAc,IAAI,OAAO,UAAU;AACnC,aAAW,IAAI,OAAO,QAAQ;AAC9B,MAAI,OAAO,aAAc,QAAO,OAAO,GAAG,OAAO,OAAO,aAAa;GACrE;;AAGJ,MAAMC,gBAAc,IAAiB,WAAwD;AAC3F,eAAc,IAAI,OAAO,MAAM;AAC/B,eAAc,IAAI,OAAO,QAAQ;AAEjC,YAAW,IAAI,OAAO,MAAM;AAC5B,YAAW,IAAI,OAAO,UAAU;AAChC,KAAI,OAAO,WAAY,QAAO,OAAO,GAAG,OAAO,OAAO,WAAW;AACjE,KAAI,OAAO,gBAAiB,IAAG,MAAM,aAAa,OAAO;AAEzD,QAAO,gBAAgB;AACrB,gBAAc,IAAI,OAAO,UAAU;AACnC,aAAW,IAAI,OAAO,QAAQ;AAC9B,MAAI,OAAO,aAAc,QAAO,OAAO,GAAG,OAAO,OAAO,aAAa;GACrE;;AAGJ,MAAMC,wBACJ,OACA,WACA,aACG;AACH,KAAI,UAAU,YAAY;AACxB,YAAU,WAAW;AACrB,YAAU,gBAAgB;AAC1B,YAAU;YACD,UAAU,WAAW;AAC9B,YAAU,WAAW;AACrB,YAAU,gBAAgB;AAC1B,YAAU;;;;;;;;;AAUd,MAAM,kBAAkB,EACtB,MACA,SAAS,OACT,UAAU,MACV,UAAU,KACV,OACA,WACA,SACA,OACA,WACA,SACA,YACA,cACA,iBACA,YACA,cACA,iBACA,SACA,cACA,SACA,cACA,eACuC;CACvC,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,EAAE,OAAO,KAAK,UAAU,aAAa,aAAa,mBAAmB;EAAE;EAAM;EAAQ,CAAC;CAE5F,MAAM,aAAa,WAAwB;CAC3C,MAAM,YAAY,UAChB,YACA,UACC,SAAS,OAAmC,IAG9C;CAED,MAAM,YAAY;EAChB;EACA;EACA;EACA;EACD;CAED,MAAM,mBAAmB;EACvB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;AAED,iBAAgB;EACd,KAAK;EACL,eAAe,OAAO,KAAK,cAAc,OAAO,KAAK,cAAc,CAAC,eAAe;EACnF;EACA,aAAa;AACX,OAAI,OAAO,KAAK,WACd,WAAU,gBAAgB;YACjB,OAAO,KAAK,UACrB,WAAU,gBAAgB;AAE5B,aAAU;;EAEb,CAAC;AAEF,aACQ,OAAO,GACZ,iBAAiB;EAChB,MAAM,KAAK,WAAW;AACtB,MAAI,CAAC,GAAI;AAET,MAAI,eAAe,EAAE;AACnB,wBAAmB,cAAc,WAAW,SAAS;AACrD;;AAGF,MAAI,iBAAiB,YAAY;AAC/B,aAAU,WAAW;GACrB,MAAM,UAAUF,aAAW,IAAI,iBAAiB;AAChD,gBAAa,qBAAqB,QAAQ;;AAG5C,MAAI,iBAAiB,WAAW;AAC9B,aAAU,WAAW;GACrB,MAAM,UAAUC,aAAW,IAAI,iBAAiB;AAChD,gBAAa,qBAAqB,QAAQ;;AAG5C,MAAI,iBAAiB,WAAW;AAC9B,iBAAc,IAAI,MAAM;AACxB,MAAG,MAAM,aAAa;;IAG1B,EAAE,WAAW,MAAM,CACpB;AAED,QACE,oBAAC,MAAD;EACE,MAAM;EACN,UACE,UACI,OACA,WAAW,UAAU;GACnB,KAAK;GACL,OAAO,YACJ,SAAS,OAAmC,OAG7C,EAAE,SAAS,QAAQ,CACpB;GACF,CAAC;YAGP,WAAW,UAAU,EAAE,KAAK,WAAW,CAAC;EACpC;;;;;ACxKX,MAAME,aAAW,UACf,SAAS,QAAQ,OAAO,UAAU,YAAY,UAAW;AAE3D,MAAM,oBAAoB,aAAoC;CAC5D,MAAM,SAAuB,EAAE;AAC/B,MAAK,MAAM,SAAS,SAClB,KAAIA,UAAQ,MAAM,EAAE;EAClB,MAAM,MAAO,MAA4C;AACzD,MAAI,OAAO,KACT,QAAO,KAAK;GAAE;GAAK,SAAS;GAAO,CAAC;;AAI1C,QAAO;;;;;;;;;;;AAYT,MAAM,iBAAiB,EACrB,QACA,WACA,QACA,SACA,WACA,eACsC;CACtC,MAAM,kBAAkB,UAAU,OAAO,UAAU;CACnD,MAAM,mBAAmB,WAAW,OAAO,WAAW;CAEtD,MAAM,0BAAU,IAAI,KAA6B;CACjD,MAAM,6BAAa,IAAI,KAA6B;CACpD,MAAM,cAAc,OAAO,EAAE;CAG7B,MAAM,cAAc,OAAO,aAAa,aAAc,iBAAmC;CAGzF,MAAM,eAAe,iBAAiB,aAAa,CAAC;CACpD,MAAM,cAAc,IAAI,IAAI,aAAa,KAAK,MAAM,EAAE,IAAI,CAAC;AAC3D,MAAK,MAAM,EAAE,KAAK,aAAa,aAC7B,SAAQ,IAAI,KAAK,QAAQ;CAG3B,MAAM,oBAAoB,QAAyB;AACjD,aAAW,OAAO,IAAI;AACtB,YAAU,gBAAgB;AAC1B,cAAY,QAAQ,MAAM,IAAI,EAAE;;AAIlC,eAAc;AACZ,eAAa;EAGb,MAAM,eAAe,iBADG,aAAa,CACiB;EACtD,MAAM,6BAAa,IAAI,KAA6B;AACpD,OAAK,MAAM,EAAE,KAAK,aAAa,aAC7B,YAAW,IAAI,KAAK,QAAQ;AAI9B,OAAK,MAAM,CAAC,KAAK,UAAU,QACzB,KAAI,CAAC,WAAW,IAAI,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAC9C,YAAW,IAAI,KAAK,MAAM;AAK9B,OAAK,MAAM,OAAO,WAAW,MAAM,CACjC,YAAW,OAAO,IAAI;AAIxB,UAAQ,OAAO;AACf,OAAK,MAAM,CAAC,KAAK,YAAY,WAC3B,SAAQ,IAAI,KAAK,QAAQ;EAI3B,MAAM,aAA2B,CAAC,GAAG,aAAa;AAClD,OAAK,MAAM,CAAC,KAAK,YAAY,WAC3B,YAAW,KAAK;GAAE;GAAK;GAAS,CAAC;EAGnC,MAAM,kBAAkB,WAAW,KAAK,EAAE,KAAK,cAAc;GAC3D,MAAM,YAAY,YAAY,IAAI,IAAI;GACtC,MAAM,YAAY,WAAW,IAAI,IAAI;AAErC,UACE,oBAAC,gBAAD;IACE,YAAY;IACZ,QAAQ,YAAY,kBAAkB;IACtC,SAAS;IACT,YAAY,OAAO;IACnB,cAAc,OAAO;IACrB,iBAAiB,OAAO;IACxB,YAAY,OAAO;IACnB,cAAc,OAAO;IACrB,iBAAiB,OAAO;IACxB,OAAO,OAAO;IACd,WAAW,OAAO;IAClB,SAAS,OAAO;IAChB,OAAO,OAAO;IACd,WAAW,OAAO;IAClB,SAAS,OAAO;IAChB,oBAAoB,iBAAiB,IAAI;cAExC;IACc;IAEnB;AAEF,SAAO,EAAE,OAAO,KAAK,EAAE,GAAG,WAAW,EAAE,GAAG,gBAAgB;;;;;;AC3H9D,MAAM,WAAW,UACf,SAAS,QAAQ,OAAO,UAAU,YAAY,UAAW;;;;;;AAO3D,MAAM,mBAAmB,EACvB,QACA,WACA,MACA,QACA,SACA,UACA,cACA,WACA,eACwC;CACxC,MAAM,kBAAkB,UAAU,OAAO,UAAU;CACnD,MAAM,mBAAmB,WAAW,OAAO,WAAW;CACtD,MAAM,oBAAoB,YAAY,OAAO,YAAY;CACzD,MAAM,wBAAwB,gBAAgB,OAAO,gBAAgB;CAErE,MAAM,cAAc,MAAM,QAAQ,SAAS,GAAG,WAAW,CAAC,SAAS,EAAE,OAAO,QAAQ;CACpF,MAAM,QAAQ,WAAW;CAEzB,MAAM,oBAAoB,WAAW,KAAK,OAAO,UAAU;EACzD,MAAM,eAAe,CAAC,MAAM,IAAI,wBAAwB,QAAQ,IAAI,QAAQ;EAC5E,MAAM,QAAQ,eAAe;AAE7B,SACE,oBAAC,gBAAD;GAEQ;GACN,QAAQ;GACR,SAAS,mBAAmB;GAC5B,YAAY,OAAO;GACnB,cAAc,OAAO;GACrB,iBAAiB,OAAO;GACxB,YAAY,OAAO;GACnB,cAAc,OAAO;GACrB,iBAAiB,OAAO;GACxB,OAAO,OAAO;GACd,WAAW,OAAO;GAClB,SAAS,OAAO;GAChB,OAAO,OAAO;GACd,WAAW,OAAO;GAClB,SAAS,OAAO;GAChB,cACE,WAAW,wBAAwB,IAAI,QAAQ,KAAK,UAAU,eAAe;aAG9E,WAAW,OAAO,EACjB,OAAO;IACL,GAAK,MAAM,OAAmC;IAC9C,mBAAmB;IACnB,sBAAsB,GAAG,kBAAkB;IAC3C,iBAAiB,GAAG,MAAM;IAC3B,EACF,CAAC;GACa,EA5BT,MAA4C,OAAO,MA4B1C;GAEnB;AAEF,QAAO,EAAE,OAAO,KAAK,EAAE,GAAG,WAAW,EAAE,GAAG,kBAAkB;;;;;AC/D9D,MAAM,cAAc,IAAiB,WAA0B;AAC7D,YAAW,IAAI,OAAO,MAAM;AAC5B,YAAW,IAAI,OAAO,UAAU;AAChC,KAAI,OAAO,WAAY,QAAO,OAAO,GAAG,OAAO,OAAO,WAAW;AACjE,KAAI,OAAO,gBAAiB,IAAG,MAAM,aAAa,OAAO;AAEzD,QAAO,gBAAgB;AACrB,gBAAc,IAAI,OAAO,UAAU;AACnC,aAAW,IAAI,OAAO,QAAQ;AAC9B,MAAI,OAAO,aAAc,QAAO,OAAO,GAAG,OAAO,OAAO,aAAa;GACrE;;AAGJ,MAAM,cAAc,IAAiB,WAA0B;AAC7D,eAAc,IAAI,OAAO,MAAM;AAC/B,eAAc,IAAI,OAAO,QAAQ;AAEjC,YAAW,IAAI,OAAO,MAAM;AAC5B,YAAW,IAAI,OAAO,UAAU;AAChC,KAAI,OAAO,WAAY,QAAO,OAAO,GAAG,OAAO,OAAO,WAAW;AACjE,KAAI,OAAO,gBAAiB,IAAG,MAAM,aAAa,OAAO;AAEzD,QAAO,gBAAgB;AACrB,gBAAc,IAAI,OAAO,UAAU;AACnC,aAAW,IAAI,OAAO,QAAQ;AAC9B,MAAI,OAAO,aAAc,QAAO,OAAO,GAAG,OAAO,OAAO,aAAa;GACrE;;AAGJ,MAAM,sBACJ,OACA,KACA,aACG;AACH,KAAI,UAAU,YAAY;AACxB,MAAI,WAAW;AACf,MAAI,gBAAgB;AACpB,YAAU;YACD,UAAU,WAAW;AAC9B,MAAI,WAAW;AACf,MAAI,gBAAgB;AACpB,YAAU;;;;;;;AAQd,MAAM,sBAAsB,EAC1B,QACA,WACA,MACA,QACA,SACA,SACA,WACA,eAC2C;CAC3C,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,EACJ,OACA,KAAK,UACL,aACA,aACE,mBAAmB;EACrB;EACA,QAAQ,UAAU,OAAO,UAAU;EACpC,CAAC;CAEF,MAAM,aAAa,WAAwB;CAC3C,MAAM,YAAY,UAAU,YAAY,SAAS;CAEjD,MAAM,mBAAmB,WAAW,OAAO,WAAW;AAGtD,iBAAgB;EACd,KAAK;EACL,eAAe,OAAO,KAAK,cAAc,OAAO,KAAK,cAAc,CAAC,eAAe;EACnF,SALuB,WAAW,OAAO,WAAW;EAMpD,aAAa;AACX,OAAI,OAAO,KAAK,WACd,WAAU,gBAAgB;YACjB,OAAO,KAAK,UACrB,WAAU,gBAAgB;AAE5B,aAAU;;EAEb,CAAC;AAEF,aACQ,OAAO,GACZ,iBAAiB;EAChB,MAAM,KAAK,WAAW;AACtB,MAAI,CAAC,GAAI;AAET,MAAI,eAAe,EAAE;AACnB,sBAAmB,cAAc,WAAW,SAAS;AACrD;;AAGF,MAAI,iBAAiB,YAAY;AAC/B,aAAU,WAAW;GACrB,MAAM,UAAU,WAAW,IAAI,OAAO;AACtC,gBAAa,qBAAqB,QAAQ;;AAG5C,MAAI,iBAAiB,WAAW;AAC9B,aAAU,WAAW;GACrB,MAAM,UAAU,WAAW,IAAI,OAAO;AACtC,gBAAa,qBAAqB,QAAQ;;AAG5C,MAAI,iBAAiB,WAAW;AAC9B,iBAAc,IAAI,OAAO,MAAM;AAC/B,MAAG,MAAM,aAAa;;IAG1B,EAAE,WAAW,MAAM,CACpB;AAED,QACE,oBAAC,MAAD;EACE,MAAM;EACN,UACE,mBACI,OACA,EACE,OAAO,KACP;GACE,KAAK;GACL,GAAG;GACH,OAAO;IACL,GAAK,UAAU,SAA2B,EAAE;IAC5C,SAAS;IACV;GACF,EACD,SACD;YAGN,EAAE,OAAO,KAAK;GAAE,KAAK;GAAW,GAAG;GAAW,EAAE,SAAS;EACrD;;;;;;AC1JX,MAAM,eAAe,IAAI,IAAI;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;AAOF,MAAM,0BACJ,WACgC;CAChC,MAAM,aAAa,UAAiD;EAElE,MAAM,YAAqC,EAAE;EAC7C,MAAM,eAAwC,EAAE;AAEhD,OAAK,MAAM,OAAO,MAChB,KAAI,aAAa,IAAI,IAAI,CACvB,cAAa,OAAO,MAAM;MAE1B,WAAU,OAAO,MAAM;EAI3B,MAAM,EACJ,MACA,QACA,SACA,SACA,YACA,UACA,cACA,SACA,cACA,SACA,iBACE;EAUJ,MAAM,YAA0C;GAC9C,SAAS,WAAW,OAAO;GAC3B,cAAc,gBAAgB,OAAO;GACrC,SAAS,WAAW,OAAO;GAC3B,cAAc,gBAAgB,OAAO;GACtC;EAGD,MAAM,EAAE,UAAU,GAAG,aAAa;AAElC,MAAI,OAAO,SAAS,WAClB,QACE,oBAAC,kBAAD;GACU;GACR,WAAW;GACL;GACE;GACC;GACG;GACD;GAEV;GACgB;AAIvB,MAAI,OAAO,SAAS,UAClB,QACE,oBAAC,iBAAD;GACU;GACR,WAAW;GACL;GACE;GACC;GACC;GACI;GACH;GAEV;GACe;AAItB,MAAI,OAAO,SAAS,QAClB,QACE,oBAAC,eAAD;GACU;GACR,WAAW;GACH;GACC;GACE;GAEV;GACa;AAKpB,SACE,oBAAC,oBAAD;GACU;GACR,WAAW;GACL;GACE;GACC;GACA;GACE;GAEV;GACkB;;AAIzB,WAAU,cAAc,WAAW,OAAO,IAAI;AAG9C,QAAO,OAAO,OAAO,WAAW;EAC9B,SAAS,WACP,uBAAkC;GAChC,GAAG;GACH,GAAG;GACJ,CAAkB;EAErB,QAAQ,WACN,uBAAkC;GAAE,GAAG;GAAQ,YAAY;GAAQ,CAAC;EAEtE,UAAU,WACR,uBAAkC;GAAE,GAAG;GAAQ,cAAc;GAAQ,CAAC;EAExE,kBAAkB,UAChB,uBAAkC;GAAE,GAAG;GAAQ,iBAAiB;GAAO,CAAC;EAE1E,QAAQ,WACN,uBAAkC;GAAE,GAAG;GAAQ,YAAY;GAAQ,CAAC;EAEtE,UAAU,WACR,uBAAkC;GAAE,GAAG;GAAQ,cAAc;GAAQ,CAAC;EAExE,kBAAkB,UAChB,uBAAkC;GAAE,GAAG;GAAQ,iBAAiB;GAAO,CAAC;EAE1E,aAAa,EAAE,QAAQ,MAAM,SAC3B,uBAAkC;GAChC,GAAG;GACH,OAAO;GACP,WAAW;GACX,SAAS;GACV,CAAC;EAEJ,aAAa,EAAE,QAAQ,MAAM,SAC3B,uBAAkC;GAChC,GAAG;GACH,OAAO;GACP,WAAW;GACX,SAAS;GACV,CAAC;EAEJ,SAAS,SACP,uBAAkC;GAChC,GAAG;GACH,GAAG;GACJ,CAAkB;EAErB,KAAK,QACH,uBAAkC;GAAE,GAAG;GAAQ,GAAG;GAAK,CAAC;EAE1D,WAAW,SACT,uBAAwC;GACtC,GAAG;GACH,MAAM;GACN,GAAG;GACJ,CAAC;EAEJ,UAAU,SACR,uBAAuC;GACrC,GAAG;GACH,MAAM;GACN,GAAG;GACJ,CAAC;EAEJ,aAAa,uBAAqC;GAAE,GAAG;GAAQ,MAAM;GAAS,CAAC;EAChF,CAAC;;;;;;;;;;;;;;;;;;;;;;;AC1LJ,MAAM,WAA+B,QACnC,uBAA0C;CAAE;CAAK,MAAM;CAAc,CAAC;;;;AClBxE,MAAa,OAAe;CAC1B,YAAY,EAAE,SAAS,GAAG;CAC1B,cAAc,EAAE,SAAS,GAAG;CAC5B,iBAAiB;CACjB,YAAY,EAAE,SAAS,GAAG;CAC1B,cAAc,EAAE,SAAS,GAAG;CAC5B,iBAAiB;CAClB;AAED,MAAa,UAAkB;CAC7B,YAAY;EAAE,SAAS;EAAG,WAAW;EAAe;CACpD,cAAc;EAAE,SAAS;EAAG,WAAW;EAAY;CACnD,iBAAiB;CACjB,YAAY;EAAE,SAAS;EAAG,WAAW;EAAY;CACjD,cAAc;EAAE,SAAS;EAAG,WAAW;EAAe;CACtD,iBAAiB;CAClB;AAED,MAAa,UAAkB;CAC7B,YAAY;EAAE,SAAS;EAAG,WAAW;EAAoB;CACzD,cAAc;EAAE,SAAS;EAAG,WAAW;EAAiB;CACxD,iBAAiB;CACjB,YAAY;EAAE,SAAS;EAAG,WAAW;EAAiB;CACtD,cAAc;EAAE,SAAS;EAAG,WAAW;EAAoB;CAC3D,iBAAiB;CAClB;AAED,MAAa,YAAoB;CAC/B,YAAY;EAAE,SAAS;EAAG,WAAW;EAAqB;CAC1D,cAAc;EAAE,SAAS;EAAG,WAAW;EAAiB;CACxD,iBAAiB;CACjB,YAAY;EAAE,SAAS;EAAG,WAAW;EAAiB;CACtD,cAAc;EAAE,SAAS;EAAG,WAAW;EAAqB;CAC5D,iBAAiB;CAClB;AAED,MAAa,YAAoB;CAC/B,YAAY;EAAE,SAAS;EAAG,WAAW;EAAoB;CACzD,cAAc;EAAE,SAAS;EAAG,WAAW;EAAiB;CACxD,iBAAiB;CACjB,YAAY;EAAE,SAAS;EAAG,WAAW;EAAiB;CACtD,cAAc;EAAE,SAAS;EAAG,WAAW;EAAoB;CAC3D,iBAAiB;CAClB;AAED,MAAa,aAAqB;CAChC,YAAY;EAAE,SAAS;EAAG,WAAW;EAAqB;CAC1D,cAAc;EAAE,SAAS;EAAG,WAAW;EAAiB;CACxD,iBAAiB;CACjB,YAAY;EAAE,SAAS;EAAG,WAAW;EAAiB;CACtD,cAAc;EAAE,SAAS;EAAG,WAAW;EAAqB;CAC5D,iBAAiB;CAClB;AAED,MAAa,UAAU;CACrB;CACA;CACA;CACA;CACA;CACA;CACD"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["h","applyEnter","applyLeave","applyReducedMotion","isVNode"],"sources":["../src/useAnimationEnd.ts","../src/useReducedMotion.ts","../../../core/core/lib/jsx-runtime.js","../src/kinetic/CollapseRenderer.tsx","../src/useTransitionState.ts","../src/utils.ts","../src/kinetic/TransitionItem.tsx","../src/kinetic/GroupRenderer.tsx","../src/kinetic/StaggerRenderer.tsx","../src/kinetic/TransitionRenderer.tsx","../src/kinetic/createKineticComponent.tsx","../src/kinetic.ts","../src/presets.ts"],"sourcesContent":["import type { Ref } from '@pyreon/core'\nimport { watch } from '@pyreon/reactivity'\n\nconst DEFAULT_TIMEOUT = 5000\n\nexport type UseAnimationEnd = (options: {\n ref: Ref<HTMLElement>\n onEnd: () => void\n active: () => boolean\n timeout?: number | undefined\n}) => void\n\nconst useAnimationEnd: UseAnimationEnd = ({ ref, onEnd, active, timeout = DEFAULT_TIMEOUT }) => {\n let called = false\n\n watch(\n active,\n (isActive) => {\n if (!isActive) {\n called = false\n return\n }\n\n const el = ref.current\n if (!el) return\n\n called = false\n\n const done = () => {\n if (called) return\n called = true\n el.removeEventListener('transitionend', handleEnd)\n el.removeEventListener('animationend', handleEnd)\n clearTimeout(timer)\n onEnd()\n }\n\n const handleEnd = (e: Event) => {\n // Ignore bubbled events from children\n if (e.target !== el) return\n done()\n }\n\n el.addEventListener('transitionend', handleEnd)\n el.addEventListener('animationend', handleEnd)\n\n const timer = setTimeout(done, timeout)\n\n return () => {\n el.removeEventListener('transitionend', handleEnd)\n el.removeEventListener('animationend', handleEnd)\n clearTimeout(timer)\n }\n },\n { immediate: true },\n )\n}\n\nexport default useAnimationEnd\n","import { onMount, onUnmount } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\n\n/**\n * Inline reduced-motion check for kinetic package.\n * Avoids depending on @pyreon/hooks for a single media query.\n */\nexport function useReducedMotion(): () => boolean {\n const matches = signal(false)\n let mql: MediaQueryList | undefined\n\n const onChange = (e: MediaQueryListEvent) => {\n matches.set(e.matches)\n }\n\n onMount(() => {\n mql = window.matchMedia('(prefers-reduced-motion: reduce)')\n matches.set(mql.matches)\n mql.addEventListener('change', onChange)\n return undefined\n })\n\n onUnmount(() => {\n mql?.removeEventListener('change', onChange)\n })\n\n return matches\n}\n","//#region src/h.ts\n/** Marker for fragment nodes — renders children without a wrapper element */\nconst Fragment = Symbol(\"Pyreon.Fragment\");\n/**\n* Hyperscript function — the compiled output of JSX.\n* `<div class=\"x\">hello</div>` → `h(\"div\", { class: \"x\" }, \"hello\")`\n*\n* Generic on P so TypeScript validates props match the component's signature\n* at the call site, then stores the result in the loosely-typed VNode.\n*/\n/** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */\nconst EMPTY_PROPS = {};\nfunction h(type, props, ...children) {\n\treturn {\n\t\ttype,\n\t\tprops: props ?? EMPTY_PROPS,\n\t\tchildren: normalizeChildren(children),\n\t\tkey: props?.key ?? null\n\t};\n}\nfunction normalizeChildren(children) {\n\tfor (let i = 0; i < children.length; i++) if (Array.isArray(children[i])) return flattenChildren(children);\n\treturn children;\n}\nfunction flattenChildren(children) {\n\tconst result = [];\n\tfor (const child of children) if (Array.isArray(child)) result.push(...flattenChildren(child));\n\telse result.push(child);\n\treturn result;\n}\n\n//#endregion\n//#region src/jsx-runtime.ts\n/**\n* JSX automatic runtime.\n*\n* When tsconfig has `\"jsxImportSource\": \"@pyreon/core\"`, the TS/bundler compiler\n* rewrites JSX to imports from this file automatically:\n* <div class=\"x\" /> → jsx(\"div\", { class: \"x\" })\n*/\nfunction jsx(type, props, key) {\n\tconst { children, ...rest } = props;\n\tconst propsWithKey = key != null ? {\n\t\t...rest,\n\t\tkey\n\t} : rest;\n\tif (typeof type === \"function\") return h(type, children !== void 0 ? {\n\t\t...propsWithKey,\n\t\tchildren\n\t} : propsWithKey);\n\treturn h(type, propsWithKey, ...children === void 0 ? [] : Array.isArray(children) ? children : [children]);\n}\nconst jsxs = jsx;\n\n//#endregion\nexport { Fragment, jsx, jsxs };\n//# sourceMappingURL=jsx-runtime.js.map","import type { VNode } from '@pyreon/core'\nimport { createRef, h, Show } from '@pyreon/core'\nimport { runUntracked, signal, watch } from '@pyreon/reactivity'\nimport type { CSSProperties, TransitionCallbacks, TransitionStage } from '../types'\nimport useAnimationEnd from '../useAnimationEnd'\nimport { useReducedMotion } from '../useReducedMotion'\nimport type { KineticConfig } from './types'\n\ntype CollapseRendererProps = {\n config: KineticConfig\n htmlProps: Record<string, unknown>\n show: () => boolean\n appear?: boolean | undefined\n timeout?: number | undefined\n transition?: string | undefined\n callbacks: Partial<TransitionCallbacks>\n children: VNode | VNode[]\n}\n\n/**\n * Renders a height-animated collapse. The config.tag becomes the outer\n * wrapper (overflow:hidden + animated height). An inner div measures\n * scrollHeight for the target value.\n */\nconst CollapseRenderer = ({\n config,\n htmlProps,\n show,\n appear,\n timeout,\n transition,\n callbacks,\n children,\n}: CollapseRendererProps): VNode | null => {\n const reducedMotion = useReducedMotion()\n let wrapperRef: { current: HTMLElement | null } = createRef<HTMLElement>()\n const contentRef = createRef<HTMLDivElement>()\n\n const effectiveAppear = appear ?? config.appear ?? false\n const effectiveTimeout = timeout ?? config.timeout ?? 5000\n const effectiveTransition = transition ?? config.transition ?? 'height 300ms ease'\n\n const initialShow = show()\n const needsAppear = effectiveAppear && initialShow\n const stage = signal<TransitionStage>(initialShow ? 'entered' : 'hidden')\n let isInitialMount = true\n let appearTriggered = false\n\n // Intercept ref assignment to trigger appear after all refs are wired\n if (needsAppear) {\n const orig = wrapperRef\n const proxy = { current: null as HTMLElement | null }\n Object.defineProperty(proxy, 'current', {\n get() {\n return orig.current\n },\n set(node: HTMLElement | null) {\n orig.current = node\n if (node && !appearTriggered) {\n appearTriggered = true\n queueMicrotask(() => stage.set('entering'))\n }\n },\n })\n wrapperRef = proxy\n }\n\n // State machine transitions\n watch(\n show,\n (showVal) => {\n if (isInitialMount) {\n isInitialMount = false\n // appear case is handled by ref proxy above\n return\n }\n\n const currentStage = runUntracked(() => stage())\n if (showVal && (currentStage === 'hidden' || currentStage === 'leaving')) {\n stage.set('entering')\n } else if (!showVal && (currentStage === 'entered' || currentStage === 'entering')) {\n stage.set('leaving')\n }\n },\n { immediate: true },\n )\n\n // Animate height\n watch(\n () => stage(),\n (currentStage) => {\n const wrapper = wrapperRef.current\n const content = contentRef.current\n if (!wrapper || !content) return\n\n if (reducedMotion()) {\n if (currentStage === 'entering') {\n callbacks.onEnter?.()\n wrapper.style.height = 'auto'\n wrapper.style.overflow = ''\n callbacks.onAfterEnter?.()\n stage.set('entered')\n } else if (currentStage === 'leaving') {\n callbacks.onLeave?.()\n wrapper.style.height = '0px'\n wrapper.style.overflow = 'hidden'\n callbacks.onAfterLeave?.()\n stage.set('hidden')\n }\n return\n }\n\n if (currentStage === 'entering') {\n callbacks.onEnter?.()\n const height = content.scrollHeight\n wrapper.style.transition = 'none'\n wrapper.style.height = '0px'\n wrapper.style.overflow = 'hidden'\n // Force reflow\n void wrapper.offsetHeight\n wrapper.style.transition = effectiveTransition\n wrapper.style.height = `${height}px`\n }\n\n if (currentStage === 'leaving') {\n callbacks.onLeave?.()\n const height = content.scrollHeight\n wrapper.style.transition = 'none'\n wrapper.style.height = `${height}px`\n wrapper.style.overflow = 'hidden'\n // Force reflow\n void wrapper.offsetHeight\n wrapper.style.transition = effectiveTransition\n wrapper.style.height = '0px'\n }\n },\n { immediate: true },\n )\n\n useAnimationEnd({\n ref: wrapperRef,\n active: () => (stage() === 'entering' || stage() === 'leaving') && !reducedMotion(),\n timeout: effectiveTimeout,\n onEnd: () => {\n const wrapper = wrapperRef.current\n if (stage() === 'entering') {\n if (wrapper) {\n wrapper.style.height = 'auto'\n wrapper.style.overflow = ''\n wrapper.style.transition = ''\n }\n callbacks.onAfterEnter?.()\n stage.set('entered')\n } else if (stage() === 'leaving') {\n callbacks.onAfterLeave?.()\n stage.set('hidden')\n }\n },\n })\n\n const shouldRender = () => stage() !== 'hidden'\n\n const wrapperStyle: CSSProperties = {\n ...((htmlProps.style as CSSProperties) ?? {}),\n ...(stage() !== 'entered' ? { overflow: 'hidden' } : {}),\n ...(stage() === 'hidden' ? { height: '0px' } : stage() === 'entered' ? { height: 'auto' } : {}),\n }\n\n return h(\n config.tag,\n { ref: wrapperRef, ...htmlProps, style: wrapperStyle },\n <Show when={shouldRender}>\n <div ref={contentRef}>{children}</div>\n </Show>,\n )\n}\n\nexport default CollapseRenderer\n","import { createRef } from '@pyreon/core'\nimport { runUntracked, signal, watch } from '@pyreon/reactivity'\nimport type { TransitionStage, TransitionStateResult } from './types'\n\nexport type UseTransitionState = (options: {\n show: () => boolean\n appear?: boolean | undefined\n}) => TransitionStateResult\n\nconst useTransitionState: UseTransitionState = ({ show, appear = false }) => {\n const initialShow = show()\n // When appear=true and show starts true, mount the element (stage='entered')\n // but defer the enter animation until the ref is connected.\n const needsAppear = appear && initialShow\n const stage = signal<TransitionStage>(initialShow ? 'entered' : 'hidden')\n const elementRef = createRef<HTMLElement>()\n let isInitialMount = true\n let appearTriggered = false\n\n // Ref callback that triggers the appear animation once the element is wired\n const refCallback = (node: HTMLElement | null) => {\n elementRef.current = node\n if (node && needsAppear && !appearTriggered) {\n appearTriggered = true\n stage.set('entering')\n }\n }\n\n watch(\n show,\n (showVal) => {\n if (isInitialMount) {\n isInitialMount = false\n // appear case is handled by refCallback above\n return\n }\n\n const currentStage = runUntracked(() => stage())\n if (showVal && (currentStage === 'hidden' || currentStage === 'leaving')) {\n stage.set('entering')\n } else if (!showVal && (currentStage === 'entered' || currentStage === 'entering')) {\n stage.set('leaving')\n }\n },\n { immediate: true },\n )\n\n const complete = () => {\n const current = stage()\n if (current === 'entering') stage.set('entered')\n if (current === 'leaving') stage.set('hidden')\n }\n\n return {\n stage,\n ref: refCallback,\n shouldMount: () => stage() !== 'hidden',\n complete,\n }\n}\n\nexport default useTransitionState\n","import type { Ref, VNode } from '@pyreon/core'\nimport type { CSSProperties } from './types'\n\nconst splitCache = new Map<string, string[]>()\nconst splitClasses = (classes: string): string[] => {\n let cached = splitCache.get(classes)\n if (!cached) {\n cached = classes.split(/\\s+/).filter(Boolean)\n splitCache.set(classes, cached)\n }\n return cached\n}\n\n/** Adds space-separated CSS classes to an element. */\nexport const addClasses = (el: HTMLElement, classes: string | undefined) => {\n if (!classes) return\n const list = splitClasses(classes)\n if (list.length > 0) el.classList.add(...list)\n}\n\n/** Removes space-separated CSS classes from an element. */\nexport const removeClasses = (el: HTMLElement, classes: string | undefined) => {\n if (!classes) return\n const list = splitClasses(classes)\n if (list.length > 0) el.classList.remove(...list)\n}\n\n/**\n * Executes callback after two animation frames (double-rAF).\n * Ensures the browser paints the current state before applying changes,\n * which is required for CSS transitions to trigger. Returns 0 on SSR —\n * the typeof-window guard makes the SSR-safety contract explicit (callers\n * are always browser-only via `onMount`, but the rule can't AST-trace it).\n */\nexport const nextFrame = (callback: () => void): number => {\n if (typeof requestAnimationFrame === 'undefined') return 0\n return requestAnimationFrame(() => {\n requestAnimationFrame(callback)\n })\n}\n\n/** Merges two className strings, filtering undefined/empty. */\nexport const mergeClassNames = (\n existing: string | undefined,\n additional: string | undefined,\n): string | undefined => {\n const parts = [existing, additional].filter(Boolean)\n return parts.length > 0 ? parts.join(' ') : undefined\n}\n\n/** Merges two CSSProperties objects, with `b` taking precedence. */\nexport const mergeStyles = (\n a: CSSProperties | undefined,\n b: CSSProperties | undefined,\n): CSSProperties | undefined => {\n if (!a && !b) return undefined\n if (!a) return b\n if (!b) return a\n return { ...a, ...b }\n}\n\n// ─── Ref & Motion Utilities ─────────────────────────────────\n\ntype RefCallback<T> = (node: T | null) => void\ntype RefLike<T> = RefCallback<T> | Ref<T>\n\n/** Merges multiple refs (callback or object) into a single callback ref. */\nexport const mergeRefs = <T>(...refs: (RefLike<T> | undefined)[]): ((node: T | null) => void) => {\n return (node: T | null) => {\n for (const ref of refs) {\n if (!ref) continue\n if (typeof ref === 'function') {\n ref(node)\n } else {\n ;(ref as { current: unknown }).current = node\n }\n }\n }\n}\n\n/** Clones a VNode with merged props. */\nexport const cloneVNode = (vnode: VNode, extraProps: Record<string, unknown>): VNode => ({\n ...vnode,\n props: { ...vnode.props, ...extraProps },\n})\n","import type { VNode } from '@pyreon/core'\nimport { createRef, Show } from '@pyreon/core'\nimport { watch } from '@pyreon/reactivity'\nimport type { ClassTransitionProps, StyleTransitionProps, TransitionCallbacks } from '../types'\nimport useAnimationEnd from '../useAnimationEnd'\nimport { useReducedMotion } from '../useReducedMotion'\nimport useTransitionState from '../useTransitionState'\nimport { addClasses, cloneVNode, mergeRefs, mergeStyles, nextFrame, removeClasses } from '../utils'\n\ntype TransitionItemProps = ClassTransitionProps &\n StyleTransitionProps &\n TransitionCallbacks & {\n show: () => boolean\n appear?: boolean | undefined\n unmount?: boolean | undefined\n timeout?: number | undefined\n delay?: number | undefined\n children: VNode\n }\n\nconst applyEnter = (el: HTMLElement, config: ClassTransitionProps & StyleTransitionProps) => {\n addClasses(el, config.enter)\n addClasses(el, config.enterFrom)\n if (config.enterStyle) Object.assign(el.style, config.enterStyle)\n if (config.enterTransition) el.style.transition = config.enterTransition\n\n return nextFrame(() => {\n removeClasses(el, config.enterFrom)\n addClasses(el, config.enterTo)\n if (config.enterToStyle) Object.assign(el.style, config.enterToStyle)\n })\n}\n\nconst applyLeave = (el: HTMLElement, config: ClassTransitionProps & StyleTransitionProps) => {\n removeClasses(el, config.enter)\n removeClasses(el, config.enterTo)\n\n addClasses(el, config.leave)\n addClasses(el, config.leaveFrom)\n if (config.leaveStyle) Object.assign(el.style, config.leaveStyle)\n if (config.leaveTransition) el.style.transition = config.leaveTransition\n\n return nextFrame(() => {\n removeClasses(el, config.leaveFrom)\n addClasses(el, config.leaveTo)\n if (config.leaveToStyle) Object.assign(el.style, config.leaveToStyle)\n })\n}\n\nconst applyReducedMotion = (\n stage: string,\n callbacks: Partial<TransitionCallbacks>,\n complete: () => void,\n) => {\n if (stage === 'entering') {\n callbacks.onEnter?.()\n callbacks.onAfterEnter?.()\n complete()\n } else if (stage === 'leaving') {\n callbacks.onLeave?.()\n callbacks.onAfterLeave?.()\n complete()\n }\n}\n\n/**\n * Internal per-child transition component. Used by StaggerRenderer and\n * GroupRenderer to give each child its own animation state.\n *\n * Uses cloneVNode to inject ref onto the child — the child must accept ref.\n */\nconst TransitionItem = (props: TransitionItemProps): VNode | null => {\n const appear = props.appear ?? false\n const unmount = props.unmount ?? true\n const timeout = props.timeout ?? 5000\n const reducedMotion = useReducedMotion()\n const { stage, ref: stateRef, shouldMount, complete } = useTransitionState({\n show: props.show,\n appear,\n })\n\n const elementRef = createRef<HTMLElement>()\n const mergedRef = mergeRefs(\n elementRef,\n stateRef,\n (props.children.props as Record<string, unknown>)?.ref as\n | ((el: HTMLElement | null) => void)\n | undefined,\n )\n\n const callbacks = {\n onEnter: props.onEnter,\n onAfterEnter: props.onAfterEnter,\n onLeave: props.onLeave,\n onAfterLeave: props.onAfterLeave,\n }\n\n const transitionConfig = {\n enter: props.enter,\n enterFrom: props.enterFrom,\n enterTo: props.enterTo,\n leave: props.leave,\n leaveFrom: props.leaveFrom,\n leaveTo: props.leaveTo,\n enterStyle: props.enterStyle,\n enterToStyle: props.enterToStyle,\n enterTransition: props.enterTransition,\n leaveStyle: props.leaveStyle,\n leaveToStyle: props.leaveToStyle,\n leaveTransition: props.leaveTransition,\n }\n\n useAnimationEnd({\n ref: elementRef,\n active: () => (stage() === 'entering' || stage() === 'leaving') && !reducedMotion(),\n timeout,\n onEnd: () => {\n if (stage() === 'entering') {\n callbacks.onAfterEnter?.()\n } else if (stage() === 'leaving') {\n callbacks.onAfterLeave?.()\n }\n complete()\n },\n })\n\n watch(\n () => stage(),\n (currentStage) => {\n const el = elementRef.current\n if (!el) return\n\n if (reducedMotion()) {\n applyReducedMotion(currentStage, callbacks, complete)\n return\n }\n\n if (currentStage === 'entering') {\n callbacks.onEnter?.()\n const frameId = applyEnter(el, transitionConfig)\n return () => cancelAnimationFrame(frameId)\n }\n\n if (currentStage === 'leaving') {\n callbacks.onLeave?.()\n const frameId = applyLeave(el, transitionConfig)\n return () => cancelAnimationFrame(frameId)\n }\n\n if (currentStage === 'entered') {\n removeClasses(el, props.enter)\n el.style.transition = ''\n }\n },\n { immediate: true },\n )\n\n return (\n <Show\n when={shouldMount}\n fallback={\n unmount\n ? null\n : cloneVNode(props.children, {\n ref: mergedRef,\n style: mergeStyles(\n (props.children.props as Record<string, unknown>)?.style as\n | Record<string, string | number | undefined>\n | undefined,\n { display: 'none' },\n ),\n })\n }\n >\n {cloneVNode(props.children, { ref: mergedRef })}\n </Show>\n )\n}\n\nexport default TransitionItem\n","import type { VNode } from '@pyreon/core'\nimport { h } from '@pyreon/core'\nimport { signal } from '@pyreon/reactivity'\nimport type { TransitionCallbacks } from '../types'\nimport TransitionItem from './TransitionItem'\nimport type { KineticConfig } from './types'\n\ntype GroupRendererProps = {\n config: KineticConfig\n htmlProps: Record<string, unknown>\n appear?: boolean | undefined\n timeout?: number | undefined\n callbacks: Partial<TransitionCallbacks>\n /**\n * Children can be a static array OR a reactive accessor `() => VNode[]`.\n * When passed as an accessor, GroupRenderer tracks changes and\n * animates entering/leaving children automatically.\n */\n children: VNode[] | (() => VNode[])\n}\n\ntype KeyedChild = { key: string | number; element: VNode }\n\nconst isVNode = (child: unknown): child is VNode =>\n child != null && typeof child === 'object' && 'type' in (child as object)\n\nconst getKeyedChildren = (children: VNode[]): KeyedChild[] => {\n const result: KeyedChild[] = []\n for (const child of children) {\n if (isVNode(child)) {\n const key = (child as VNode & { key?: string | number }).key\n if (key != null) {\n result.push({ key, element: child })\n }\n }\n }\n return result\n}\n\n/**\n * Renders children with key-based enter/exit animation (no `show` prop).\n * Children that appear (new key) animate in. Children that disappear\n * (removed key) stay in DOM during leave animation, then unmount.\n * config.tag wraps all children as a container element.\n *\n * In Pyreon, components run once. Pass children as a reactive accessor\n * `() => VNode[]` for the group to detect changes and animate entries/exits.\n */\nconst GroupRenderer = ({\n config,\n htmlProps,\n appear,\n timeout,\n callbacks,\n children,\n}: GroupRendererProps): VNode | null => {\n const effectiveAppear = appear ?? config.appear ?? false\n const effectiveTimeout = timeout ?? config.timeout ?? 5000\n\n const prevMap = new Map<string | number, VNode>()\n const leavingMap = new Map<string | number, VNode>()\n const forceUpdate = signal(0)\n\n // Normalize children to an accessor\n const getChildren = typeof children === 'function' ? (children as () => VNode[]) : () => children\n\n // Track initial keys for appear animation logic\n const initialKeyed = getKeyedChildren(getChildren())\n const initialKeys = new Set(initialKeyed.map((c) => c.key))\n for (const { key, element } of initialKeyed) {\n prevMap.set(key, element)\n }\n\n const handleAfterLeave = (key: string | number) => {\n leavingMap.delete(key)\n callbacks.onAfterLeave?.()\n forceUpdate.update((c) => c + 1)\n }\n\n // Reactive accessor — re-evaluates when children() or forceUpdate changes\n return (() => {\n forceUpdate()\n\n const currentChildren = getChildren()\n const currentKeyed = getKeyedChildren(currentChildren)\n const currentMap = new Map<string | number, VNode>()\n for (const { key, element } of currentKeyed) {\n currentMap.set(key, element)\n }\n\n // Detect leaving children\n for (const [key, child] of prevMap) {\n if (!currentMap.has(key) && !leavingMap.has(key)) {\n leavingMap.set(key, child)\n }\n }\n\n // Cancel leave if child reappears\n for (const key of currentMap.keys()) {\n leavingMap.delete(key)\n }\n\n // Update prev for next diff\n prevMap.clear()\n for (const [key, element] of currentMap) {\n prevMap.set(key, element)\n }\n\n // Merge current + leaving\n const allEntries: KeyedChild[] = [...currentKeyed]\n for (const [key, element] of leavingMap) {\n allEntries.push({ key, element })\n }\n\n const groupedChildren = allEntries.map(({ key, element }) => {\n const isInitial = initialKeys.has(key)\n const isShowing = currentMap.has(key)\n\n return (\n <TransitionItem\n show={() => isShowing}\n appear={isInitial ? effectiveAppear : true}\n timeout={effectiveTimeout}\n enterStyle={config.enterStyle}\n enterToStyle={config.enterToStyle}\n enterTransition={config.enterTransition}\n leaveStyle={config.leaveStyle}\n leaveToStyle={config.leaveToStyle}\n leaveTransition={config.leaveTransition}\n enter={config.enter}\n enterFrom={config.enterFrom}\n enterTo={config.enterTo}\n leave={config.leave}\n leaveFrom={config.leaveFrom}\n leaveTo={config.leaveTo}\n onAfterLeave={() => handleAfterLeave(key)}\n >\n {element}\n </TransitionItem>\n )\n })\n\n return h(config.tag, { ...htmlProps }, ...groupedChildren)\n }) as unknown as VNode\n}\n\nexport default GroupRenderer\n","import type { VNode } from '@pyreon/core'\nimport { h } from '@pyreon/core'\nimport type { CSSProperties, TransitionCallbacks } from '../types'\nimport { cloneVNode } from '../utils'\nimport TransitionItem from './TransitionItem'\nimport type { KineticConfig } from './types'\n\ntype StaggerRendererProps = {\n config: KineticConfig\n htmlProps: Record<string, unknown>\n show: () => boolean\n appear?: boolean | undefined\n timeout?: number | undefined\n interval?: number | undefined\n reverseLeave?: boolean | undefined\n callbacks: Partial<TransitionCallbacks>\n children: VNode[]\n}\n\nconst isVNode = (child: unknown): child is VNode =>\n child != null && typeof child === 'object' && 'type' in (child as object)\n\n/**\n * Renders children with staggered enter/exit animation.\n * config.tag wraps the staggered children as a container element.\n * Each child is individually animated via TransitionItem.\n */\nconst StaggerRenderer = ({\n config,\n htmlProps,\n show,\n appear,\n timeout,\n interval,\n reverseLeave,\n callbacks,\n children,\n}: StaggerRendererProps): VNode | null => {\n const effectiveAppear = appear ?? config.appear ?? false\n const effectiveTimeout = timeout ?? config.timeout ?? 5000\n const effectiveInterval = interval ?? config.interval ?? 50\n const effectiveReverseLeave = reverseLeave ?? config.reverseLeave ?? false\n\n const childArray = (Array.isArray(children) ? children : [children]).filter(isVNode)\n const count = childArray.length\n\n const staggeredChildren = childArray.map((child, index) => {\n const staggerIndex = !show() && effectiveReverseLeave ? count - 1 - index : index\n const delay = staggerIndex * effectiveInterval\n\n return (\n <TransitionItem\n key={(child as VNode & { key?: string | number }).key ?? index}\n show={show}\n appear={effectiveAppear}\n timeout={effectiveTimeout + delay}\n enterStyle={config.enterStyle}\n enterToStyle={config.enterToStyle}\n enterTransition={config.enterTransition}\n leaveStyle={config.leaveStyle}\n leaveToStyle={config.leaveToStyle}\n leaveTransition={config.leaveTransition}\n enter={config.enter}\n enterFrom={config.enterFrom}\n enterTo={config.enterTo}\n leave={config.leave}\n leaveFrom={config.leaveFrom}\n leaveTo={config.leaveTo}\n onAfterLeave={\n index === (effectiveReverseLeave ? 0 : count - 1) ? callbacks.onAfterLeave : undefined\n }\n >\n {cloneVNode(child, {\n style: {\n ...((child.props as Record<string, unknown>)?.style as CSSProperties | undefined),\n '--stagger-index': staggerIndex,\n '--stagger-interval': `${effectiveInterval}ms`,\n transitionDelay: `${delay}ms`,\n } as CSSProperties,\n })}\n </TransitionItem>\n )\n })\n\n return h(config.tag, { ...htmlProps }, ...staggeredChildren)\n}\n\nexport default StaggerRenderer\n","import type { VNode } from '@pyreon/core'\nimport { createRef, h, Show } from '@pyreon/core'\nimport { watch } from '@pyreon/reactivity'\nimport type { CSSProperties, TransitionCallbacks } from '../types'\nimport useAnimationEnd from '../useAnimationEnd'\nimport { useReducedMotion } from '../useReducedMotion'\nimport useTransitionState from '../useTransitionState'\nimport { addClasses, mergeRefs, nextFrame, removeClasses } from '../utils'\nimport type { KineticConfig } from './types'\n\ntype TransitionRendererProps = {\n config: KineticConfig\n htmlProps: Record<string, unknown>\n show: () => boolean\n appear?: boolean | undefined\n unmount?: boolean | undefined\n timeout?: number | undefined\n callbacks: Partial<TransitionCallbacks>\n children: VNode | VNode[]\n}\n\nconst applyEnter = (el: HTMLElement, config: KineticConfig) => {\n addClasses(el, config.enter)\n addClasses(el, config.enterFrom)\n if (config.enterStyle) Object.assign(el.style, config.enterStyle)\n if (config.enterTransition) el.style.transition = config.enterTransition\n\n return nextFrame(() => {\n removeClasses(el, config.enterFrom)\n addClasses(el, config.enterTo)\n if (config.enterToStyle) Object.assign(el.style, config.enterToStyle)\n })\n}\n\nconst applyLeave = (el: HTMLElement, config: KineticConfig) => {\n removeClasses(el, config.enter)\n removeClasses(el, config.enterTo)\n\n addClasses(el, config.leave)\n addClasses(el, config.leaveFrom)\n if (config.leaveStyle) Object.assign(el.style, config.leaveStyle)\n if (config.leaveTransition) el.style.transition = config.leaveTransition\n\n return nextFrame(() => {\n removeClasses(el, config.leaveFrom)\n addClasses(el, config.leaveTo)\n if (config.leaveToStyle) Object.assign(el.style, config.leaveToStyle)\n })\n}\n\nconst applyReducedMotion = (\n stage: string,\n cbs: Partial<TransitionCallbacks>,\n complete: () => void,\n) => {\n if (stage === 'entering') {\n cbs.onEnter?.()\n cbs.onAfterEnter?.()\n complete()\n } else if (stage === 'leaving') {\n cbs.onLeave?.()\n cbs.onAfterLeave?.()\n complete()\n }\n}\n\n/**\n * Renders a single element with CSS transition enter/exit animation.\n * Uses h(config.tag) — no cloneElement needed.\n */\nconst TransitionRenderer = (props: TransitionRendererProps): VNode | null => {\n const reducedMotion = useReducedMotion()\n const {\n stage,\n ref: stateRef,\n shouldMount,\n complete,\n } = useTransitionState({\n show: props.show,\n appear: props.appear ?? props.config.appear ?? false,\n })\n\n const elementRef = createRef<HTMLElement>()\n const mergedRef = mergeRefs(elementRef, stateRef)\n\n const effectiveUnmount = props.unmount ?? props.config.unmount ?? true\n const effectiveTimeout = props.timeout ?? props.config.timeout ?? 5000\n\n useAnimationEnd({\n ref: elementRef,\n active: () => (stage() === 'entering' || stage() === 'leaving') && !reducedMotion(),\n timeout: effectiveTimeout,\n onEnd: () => {\n if (stage() === 'entering') {\n props.callbacks.onAfterEnter?.()\n } else if (stage() === 'leaving') {\n props.callbacks.onAfterLeave?.()\n }\n complete()\n },\n })\n\n watch(\n () => stage(),\n (currentStage) => {\n const el = elementRef.current\n if (!el) return\n\n if (reducedMotion()) {\n applyReducedMotion(currentStage, props.callbacks, complete)\n return\n }\n\n if (currentStage === 'entering') {\n props.callbacks.onEnter?.()\n const frameId = applyEnter(el, props.config)\n return () => cancelAnimationFrame(frameId)\n }\n\n if (currentStage === 'leaving') {\n props.callbacks.onLeave?.()\n const frameId = applyLeave(el, props.config)\n return () => cancelAnimationFrame(frameId)\n }\n\n if (currentStage === 'entered') {\n removeClasses(el, props.config.enter)\n el.style.transition = ''\n }\n },\n { immediate: true },\n )\n\n return (\n <Show\n when={shouldMount}\n fallback={\n effectiveUnmount\n ? null\n : h(\n props.config.tag,\n {\n ref: mergedRef,\n ...props.htmlProps,\n style: {\n ...((props.htmlProps.style as CSSProperties) ?? {}),\n display: 'none',\n },\n },\n props.children,\n )\n }\n >\n {h(props.config.tag, { ref: mergedRef, ...props.htmlProps }, props.children)}\n </Show>\n )\n}\n\nexport default TransitionRenderer\n","import type { VNode } from '@pyreon/core'\nimport type { CSSProperties, TransitionCallbacks } from '../types'\nimport CollapseRenderer from './CollapseRenderer'\nimport GroupRenderer from './GroupRenderer'\nimport StaggerRenderer from './StaggerRenderer'\nimport TransitionRenderer from './TransitionRenderer'\nimport type { ClassConfig, KineticComponent, KineticConfig, KineticMode } from './types'\n\n/** Keys that are kinetic-specific and should not be forwarded as HTML attrs. */\nconst KINETIC_KEYS = new Set([\n 'show',\n 'appear',\n 'unmount',\n 'timeout',\n 'transition',\n 'interval',\n 'reverseLeave',\n 'onEnter',\n 'onAfterEnter',\n 'onLeave',\n 'onAfterLeave',\n])\n\n/**\n * Core factory. Creates a component that delegates to the appropriate\n * renderer based on config.mode, then attaches immutable chain methods\n * via Object.assign.\n */\nconst createKineticComponent = <Tag extends string, Mode extends KineticMode = 'transition'>(\n config: KineticConfig,\n): KineticComponent<Tag, Mode> => {\n const Component = (props: Record<string, unknown>): VNode | null => {\n // Separate kinetic-specific props from HTML pass-through props\n const htmlProps: Record<string, unknown> = {}\n const kineticProps: Record<string, unknown> = {}\n\n for (const key in props) {\n if (KINETIC_KEYS.has(key)) {\n kineticProps[key] = props[key]\n } else {\n htmlProps[key] = props[key]\n }\n }\n\n const {\n show,\n appear,\n unmount,\n timeout,\n transition,\n interval,\n reverseLeave,\n onEnter,\n onAfterEnter,\n onLeave,\n onAfterLeave,\n } = kineticProps as {\n show?: () => boolean\n appear?: boolean\n unmount?: boolean\n timeout?: number\n transition?: string\n interval?: number\n reverseLeave?: boolean\n } & Partial<TransitionCallbacks>\n\n const callbacks: Partial<TransitionCallbacks> = {\n onEnter: onEnter ?? config.onEnter,\n onAfterEnter: onAfterEnter ?? config.onAfterEnter,\n onLeave: onLeave ?? config.onLeave,\n onAfterLeave: onAfterLeave ?? config.onAfterLeave,\n }\n\n // Extract children from htmlProps (it's not an HTML attribute)\n const { children, ...restHtml } = htmlProps\n\n if (config.mode === 'collapse') {\n return (\n <CollapseRenderer\n config={config}\n htmlProps={restHtml}\n show={show as () => boolean}\n appear={appear}\n timeout={timeout}\n transition={transition}\n callbacks={callbacks}\n >\n {children as VNode | VNode[]}\n </CollapseRenderer>\n )\n }\n\n if (config.mode === 'stagger') {\n return (\n <StaggerRenderer\n config={config}\n htmlProps={restHtml}\n show={show as () => boolean}\n appear={appear}\n timeout={timeout}\n interval={interval}\n reverseLeave={reverseLeave}\n callbacks={callbacks}\n >\n {children as VNode[]}\n </StaggerRenderer>\n )\n }\n\n if (config.mode === 'group') {\n return (\n <GroupRenderer\n config={config}\n htmlProps={restHtml}\n appear={appear}\n timeout={timeout}\n callbacks={callbacks}\n >\n {children as VNode[]}\n </GroupRenderer>\n )\n }\n\n // Default: transition mode\n return (\n <TransitionRenderer\n config={config}\n htmlProps={restHtml}\n show={show as () => boolean}\n appear={appear}\n unmount={unmount}\n timeout={timeout}\n callbacks={callbacks}\n >\n {children as VNode | VNode[]}\n </TransitionRenderer>\n )\n }\n\n Component.displayName = `kinetic(${config.tag})`\n\n // Immutable chain methods — each returns a new component with merged config.\n return Object.assign(Component, {\n preset: (preset: Record<string, unknown>) =>\n createKineticComponent<Tag, Mode>({\n ...config,\n ...preset,\n } as KineticConfig),\n\n enter: (styles: CSSProperties) =>\n createKineticComponent<Tag, Mode>({ ...config, enterStyle: styles }),\n\n enterTo: (styles: CSSProperties) =>\n createKineticComponent<Tag, Mode>({ ...config, enterToStyle: styles }),\n\n enterTransition: (value: string) =>\n createKineticComponent<Tag, Mode>({ ...config, enterTransition: value }),\n\n leave: (styles: CSSProperties) =>\n createKineticComponent<Tag, Mode>({ ...config, leaveStyle: styles }),\n\n leaveTo: (styles: CSSProperties) =>\n createKineticComponent<Tag, Mode>({ ...config, leaveToStyle: styles }),\n\n leaveTransition: (value: string) =>\n createKineticComponent<Tag, Mode>({ ...config, leaveTransition: value }),\n\n enterClass: ({ active, from, to }: ClassConfig) =>\n createKineticComponent<Tag, Mode>({\n ...config,\n enter: active,\n enterFrom: from,\n enterTo: to,\n }),\n\n leaveClass: ({ active, from, to }: ClassConfig) =>\n createKineticComponent<Tag, Mode>({\n ...config,\n leave: active,\n leaveFrom: from,\n leaveTo: to,\n }),\n\n config: (opts: Record<string, unknown>) =>\n createKineticComponent<Tag, Mode>({\n ...config,\n ...opts,\n } as KineticConfig),\n\n on: (cbs: Partial<TransitionCallbacks>) =>\n createKineticComponent<Tag, Mode>({ ...config, ...cbs }),\n\n collapse: (opts?: { transition?: string }) =>\n createKineticComponent<Tag, 'collapse'>({\n ...config,\n mode: 'collapse',\n ...opts,\n }),\n\n stagger: (opts?: { interval?: number; reverseLeave?: boolean }) =>\n createKineticComponent<Tag, 'stagger'>({\n ...config,\n mode: 'stagger',\n ...opts,\n }),\n\n group: () => createKineticComponent<Tag, 'group'>({ ...config, mode: 'group' }),\n }) as unknown as KineticComponent<Tag, Mode>\n}\n\nexport default createKineticComponent\n","import createKineticComponent from './kinetic/createKineticComponent'\nimport type { KineticComponent } from './kinetic/types'\n\n/**\n * Creates a reusable animated component via immutable chaining.\n *\n * @example\n * ```tsx\n * // Transition (default)\n * const FadeDiv = kinetic('div').preset(fade)\n *\n * // Collapse\n * const Accordion = kinetic('div').collapse()\n *\n * // Stagger\n * const StaggerList = kinetic('ul').preset(slideUp).stagger({ interval: 50 })\n *\n * // Group (key-based enter/exit)\n * const AnimatedList = kinetic('ul').preset(fade).group()\n * ```\n */\nconst kinetic = <Tag extends string>(tag: Tag): KineticComponent<Tag, 'transition'> =>\n createKineticComponent<Tag, 'transition'>({ tag, mode: 'transition' })\n\nexport default kinetic\n","import type { ClassTransitionProps, StyleTransitionProps } from './types'\n\nexport type Preset = StyleTransitionProps & ClassTransitionProps\n\nexport const fade: Preset = {\n enterStyle: { opacity: 0 },\n enterToStyle: { opacity: 1 },\n enterTransition: 'opacity 300ms ease-out',\n leaveStyle: { opacity: 1 },\n leaveToStyle: { opacity: 0 },\n leaveTransition: 'opacity 200ms ease-in',\n}\n\nexport const scaleIn: Preset = {\n enterStyle: { opacity: 0, transform: 'scale(0.95)' },\n enterToStyle: { opacity: 1, transform: 'scale(1)' },\n enterTransition: 'opacity 300ms ease-out, transform 300ms ease-out',\n leaveStyle: { opacity: 1, transform: 'scale(1)' },\n leaveToStyle: { opacity: 0, transform: 'scale(0.95)' },\n leaveTransition: 'opacity 200ms ease-in, transform 200ms ease-in',\n}\n\nexport const slideUp: Preset = {\n enterStyle: { opacity: 0, transform: 'translateY(16px)' },\n enterToStyle: { opacity: 1, transform: 'translateY(0)' },\n enterTransition: 'opacity 300ms ease-out, transform 300ms ease-out',\n leaveStyle: { opacity: 1, transform: 'translateY(0)' },\n leaveToStyle: { opacity: 0, transform: 'translateY(16px)' },\n leaveTransition: 'opacity 200ms ease-in, transform 200ms ease-in',\n}\n\nexport const slideDown: Preset = {\n enterStyle: { opacity: 0, transform: 'translateY(-16px)' },\n enterToStyle: { opacity: 1, transform: 'translateY(0)' },\n enterTransition: 'opacity 300ms ease-out, transform 300ms ease-out',\n leaveStyle: { opacity: 1, transform: 'translateY(0)' },\n leaveToStyle: { opacity: 0, transform: 'translateY(-16px)' },\n leaveTransition: 'opacity 200ms ease-in, transform 200ms ease-in',\n}\n\nexport const slideLeft: Preset = {\n enterStyle: { opacity: 0, transform: 'translateX(16px)' },\n enterToStyle: { opacity: 1, transform: 'translateX(0)' },\n enterTransition: 'opacity 300ms ease-out, transform 300ms ease-out',\n leaveStyle: { opacity: 1, transform: 'translateX(0)' },\n leaveToStyle: { opacity: 0, transform: 'translateX(16px)' },\n leaveTransition: 'opacity 200ms ease-in, transform 200ms ease-in',\n}\n\nexport const slideRight: Preset = {\n enterStyle: { opacity: 0, transform: 'translateX(-16px)' },\n enterToStyle: { opacity: 1, transform: 'translateX(0)' },\n enterTransition: 'opacity 300ms ease-out, transform 300ms ease-out',\n leaveStyle: { opacity: 1, transform: 'translateX(0)' },\n leaveToStyle: { opacity: 0, transform: 'translateX(-16px)' },\n leaveTransition: 'opacity 200ms ease-in, transform 200ms ease-in',\n}\n\nexport const presets = {\n fade,\n scaleIn,\n slideUp,\n slideDown,\n slideLeft,\n slideRight,\n} as const\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/kinetic",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.14",
|
|
4
4
|
"description": "CSS-transition-based animation components for Pyreon",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,18 +35,24 @@
|
|
|
35
35
|
"build:watch": "bun run vl_rolldown_build-watch",
|
|
36
36
|
"lint": "oxlint .",
|
|
37
37
|
"test": "vitest run",
|
|
38
|
+
"test:browser": "vitest run --config ./vitest.browser.config.ts",
|
|
38
39
|
"test:coverage": "vitest run --coverage",
|
|
39
40
|
"test:watch": "vitest",
|
|
40
41
|
"typecheck": "tsc --noEmit"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
|
-
"@pyreon/
|
|
44
|
+
"@pyreon/core": "^0.12.14",
|
|
45
|
+
"@pyreon/reactivity": "^0.12.14",
|
|
46
|
+
"@pyreon/runtime-dom": "^0.12.14",
|
|
47
|
+
"@pyreon/test-utils": "^0.12.10",
|
|
48
|
+
"@pyreon/typescript": "^0.12.14",
|
|
49
|
+
"@vitest/browser-playwright": "^4.1.4",
|
|
44
50
|
"@vitus-labs/tools-rolldown": "^1.15.3"
|
|
45
51
|
},
|
|
46
52
|
"peerDependencies": {
|
|
47
|
-
"@pyreon/core": "^0.12.
|
|
48
|
-
"@pyreon/reactivity": "^0.12.
|
|
49
|
-
"@pyreon/runtime-dom": "^0.12.
|
|
53
|
+
"@pyreon/core": "^0.12.14",
|
|
54
|
+
"@pyreon/reactivity": "^0.12.14",
|
|
55
|
+
"@pyreon/runtime-dom": "^0.12.14"
|
|
50
56
|
},
|
|
51
57
|
"engines": {
|
|
52
58
|
"node": ">= 22"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** @jsxImportSource @pyreon/core */
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { signal } from '@pyreon/reactivity'
|
|
4
|
+
import { flush, mountInBrowser } from '@pyreon/test-utils/browser'
|
|
5
|
+
import { nextFrame, mergeClassNames } from '../utils'
|
|
6
|
+
import Transition from '../Transition'
|
|
7
|
+
|
|
8
|
+
describe('@pyreon/kinetic browser smoke', () => {
|
|
9
|
+
it('Transition mounts a visible child into real DOM', async () => {
|
|
10
|
+
const show = signal(true)
|
|
11
|
+
const { container, unmount } = mountInBrowser(
|
|
12
|
+
<Transition show={show}>
|
|
13
|
+
<div data-id="t">hello</div>
|
|
14
|
+
</Transition>,
|
|
15
|
+
)
|
|
16
|
+
const el = container.querySelector('[data-id="t"]')
|
|
17
|
+
expect(el?.textContent).toBe('hello')
|
|
18
|
+
unmount()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('Transition fires onLeave when show signal goes true → false', async () => {
|
|
22
|
+
let onLeaveCalls = 0
|
|
23
|
+
const show = signal(true)
|
|
24
|
+
const { unmount } = mountInBrowser(
|
|
25
|
+
<Transition show={show} onLeave={() => { onLeaveCalls++ }}>
|
|
26
|
+
<div data-id="t">hi</div>
|
|
27
|
+
</Transition>,
|
|
28
|
+
)
|
|
29
|
+
expect(onLeaveCalls).toBe(0)
|
|
30
|
+
show.set(false)
|
|
31
|
+
await flush()
|
|
32
|
+
await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r(null))))
|
|
33
|
+
// onLeave fires once the leave transition starts — asserts the
|
|
34
|
+
// signal → stage machine → lifecycle callback path ran end-to-end.
|
|
35
|
+
expect(onLeaveCalls).toBe(1)
|
|
36
|
+
unmount()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('nextFrame schedules a callback via requestAnimationFrame', async () => {
|
|
40
|
+
let fired = false
|
|
41
|
+
nextFrame(() => {
|
|
42
|
+
fired = true
|
|
43
|
+
})
|
|
44
|
+
await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r(null))))
|
|
45
|
+
expect(fired).toBe(true)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('mergeClassNames filters empty + joins', () => {
|
|
49
|
+
expect(mergeClassNames('a', 'b')).toBe('a b')
|
|
50
|
+
expect(mergeClassNames('a', undefined)).toBe('a')
|
|
51
|
+
expect(mergeClassNames(undefined, undefined)).toBe(undefined)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('runs in a real browser — `typeof process` is undefined, `import.meta.env.DEV` is true', () => {
|
|
55
|
+
expect(typeof process).toBe('undefined')
|
|
56
|
+
expect(import.meta.env.DEV).toBe(true)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
@@ -69,61 +69,45 @@ const applyReducedMotion = (
|
|
|
69
69
|
*
|
|
70
70
|
* Uses cloneVNode to inject ref onto the child — the child must accept ref.
|
|
71
71
|
*/
|
|
72
|
-
const TransitionItem = ({
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
timeout = 5000,
|
|
77
|
-
enter,
|
|
78
|
-
enterFrom,
|
|
79
|
-
enterTo,
|
|
80
|
-
leave,
|
|
81
|
-
leaveFrom,
|
|
82
|
-
leaveTo,
|
|
83
|
-
enterStyle,
|
|
84
|
-
enterToStyle,
|
|
85
|
-
enterTransition,
|
|
86
|
-
leaveStyle,
|
|
87
|
-
leaveToStyle,
|
|
88
|
-
leaveTransition,
|
|
89
|
-
onEnter,
|
|
90
|
-
onAfterEnter,
|
|
91
|
-
onLeave,
|
|
92
|
-
onAfterLeave,
|
|
93
|
-
children,
|
|
94
|
-
}: TransitionItemProps): VNode | null => {
|
|
72
|
+
const TransitionItem = (props: TransitionItemProps): VNode | null => {
|
|
73
|
+
const appear = props.appear ?? false
|
|
74
|
+
const unmount = props.unmount ?? true
|
|
75
|
+
const timeout = props.timeout ?? 5000
|
|
95
76
|
const reducedMotion = useReducedMotion()
|
|
96
|
-
const { stage, ref: stateRef, shouldMount, complete } = useTransitionState({
|
|
77
|
+
const { stage, ref: stateRef, shouldMount, complete } = useTransitionState({
|
|
78
|
+
show: props.show,
|
|
79
|
+
appear,
|
|
80
|
+
})
|
|
97
81
|
|
|
98
82
|
const elementRef = createRef<HTMLElement>()
|
|
99
83
|
const mergedRef = mergeRefs(
|
|
100
84
|
elementRef,
|
|
101
85
|
stateRef,
|
|
102
|
-
(children.props as Record<string, unknown>)?.ref as
|
|
86
|
+
(props.children.props as Record<string, unknown>)?.ref as
|
|
103
87
|
| ((el: HTMLElement | null) => void)
|
|
104
88
|
| undefined,
|
|
105
89
|
)
|
|
106
90
|
|
|
107
91
|
const callbacks = {
|
|
108
|
-
onEnter,
|
|
109
|
-
onAfterEnter,
|
|
110
|
-
onLeave,
|
|
111
|
-
onAfterLeave,
|
|
92
|
+
onEnter: props.onEnter,
|
|
93
|
+
onAfterEnter: props.onAfterEnter,
|
|
94
|
+
onLeave: props.onLeave,
|
|
95
|
+
onAfterLeave: props.onAfterLeave,
|
|
112
96
|
}
|
|
113
97
|
|
|
114
98
|
const transitionConfig = {
|
|
115
|
-
enter,
|
|
116
|
-
enterFrom,
|
|
117
|
-
enterTo,
|
|
118
|
-
leave,
|
|
119
|
-
leaveFrom,
|
|
120
|
-
leaveTo,
|
|
121
|
-
enterStyle,
|
|
122
|
-
enterToStyle,
|
|
123
|
-
enterTransition,
|
|
124
|
-
leaveStyle,
|
|
125
|
-
leaveToStyle,
|
|
126
|
-
leaveTransition,
|
|
99
|
+
enter: props.enter,
|
|
100
|
+
enterFrom: props.enterFrom,
|
|
101
|
+
enterTo: props.enterTo,
|
|
102
|
+
leave: props.leave,
|
|
103
|
+
leaveFrom: props.leaveFrom,
|
|
104
|
+
leaveTo: props.leaveTo,
|
|
105
|
+
enterStyle: props.enterStyle,
|
|
106
|
+
enterToStyle: props.enterToStyle,
|
|
107
|
+
enterTransition: props.enterTransition,
|
|
108
|
+
leaveStyle: props.leaveStyle,
|
|
109
|
+
leaveToStyle: props.leaveToStyle,
|
|
110
|
+
leaveTransition: props.leaveTransition,
|
|
127
111
|
}
|
|
128
112
|
|
|
129
113
|
useAnimationEnd({
|
|
@@ -164,7 +148,7 @@ const TransitionItem = ({
|
|
|
164
148
|
}
|
|
165
149
|
|
|
166
150
|
if (currentStage === 'entered') {
|
|
167
|
-
removeClasses(el, enter)
|
|
151
|
+
removeClasses(el, props.enter)
|
|
168
152
|
el.style.transition = ''
|
|
169
153
|
}
|
|
170
154
|
},
|
|
@@ -177,10 +161,10 @@ const TransitionItem = ({
|
|
|
177
161
|
fallback={
|
|
178
162
|
unmount
|
|
179
163
|
? null
|
|
180
|
-
: cloneVNode(children, {
|
|
164
|
+
: cloneVNode(props.children, {
|
|
181
165
|
ref: mergedRef,
|
|
182
166
|
style: mergeStyles(
|
|
183
|
-
(children.props as Record<string, unknown>)?.style as
|
|
167
|
+
(props.children.props as Record<string, unknown>)?.style as
|
|
184
168
|
| Record<string, string | number | undefined>
|
|
185
169
|
| undefined,
|
|
186
170
|
{ display: 'none' },
|
|
@@ -188,7 +172,7 @@ const TransitionItem = ({
|
|
|
188
172
|
})
|
|
189
173
|
}
|
|
190
174
|
>
|
|
191
|
-
{cloneVNode(children, { ref: mergedRef })}
|
|
175
|
+
{cloneVNode(props.children, { ref: mergedRef })}
|
|
192
176
|
</Show>
|
|
193
177
|
)
|
|
194
178
|
}
|
|
@@ -68,16 +68,7 @@ const applyReducedMotion = (
|
|
|
68
68
|
* Renders a single element with CSS transition enter/exit animation.
|
|
69
69
|
* Uses h(config.tag) — no cloneElement needed.
|
|
70
70
|
*/
|
|
71
|
-
const TransitionRenderer = ({
|
|
72
|
-
config,
|
|
73
|
-
htmlProps,
|
|
74
|
-
show,
|
|
75
|
-
appear,
|
|
76
|
-
unmount,
|
|
77
|
-
timeout,
|
|
78
|
-
callbacks,
|
|
79
|
-
children,
|
|
80
|
-
}: TransitionRendererProps): VNode | null => {
|
|
71
|
+
const TransitionRenderer = (props: TransitionRendererProps): VNode | null => {
|
|
81
72
|
const reducedMotion = useReducedMotion()
|
|
82
73
|
const {
|
|
83
74
|
stage,
|
|
@@ -85,15 +76,15 @@ const TransitionRenderer = ({
|
|
|
85
76
|
shouldMount,
|
|
86
77
|
complete,
|
|
87
78
|
} = useTransitionState({
|
|
88
|
-
show,
|
|
89
|
-
appear: appear ?? config.appear ?? false,
|
|
79
|
+
show: props.show,
|
|
80
|
+
appear: props.appear ?? props.config.appear ?? false,
|
|
90
81
|
})
|
|
91
82
|
|
|
92
83
|
const elementRef = createRef<HTMLElement>()
|
|
93
84
|
const mergedRef = mergeRefs(elementRef, stateRef)
|
|
94
85
|
|
|
95
|
-
const effectiveUnmount = unmount ?? config.unmount ?? true
|
|
96
|
-
const effectiveTimeout = timeout ?? config.timeout ?? 5000
|
|
86
|
+
const effectiveUnmount = props.unmount ?? props.config.unmount ?? true
|
|
87
|
+
const effectiveTimeout = props.timeout ?? props.config.timeout ?? 5000
|
|
97
88
|
|
|
98
89
|
useAnimationEnd({
|
|
99
90
|
ref: elementRef,
|
|
@@ -101,9 +92,9 @@ const TransitionRenderer = ({
|
|
|
101
92
|
timeout: effectiveTimeout,
|
|
102
93
|
onEnd: () => {
|
|
103
94
|
if (stage() === 'entering') {
|
|
104
|
-
callbacks.onAfterEnter?.()
|
|
95
|
+
props.callbacks.onAfterEnter?.()
|
|
105
96
|
} else if (stage() === 'leaving') {
|
|
106
|
-
callbacks.onAfterLeave?.()
|
|
97
|
+
props.callbacks.onAfterLeave?.()
|
|
107
98
|
}
|
|
108
99
|
complete()
|
|
109
100
|
},
|
|
@@ -116,24 +107,24 @@ const TransitionRenderer = ({
|
|
|
116
107
|
if (!el) return
|
|
117
108
|
|
|
118
109
|
if (reducedMotion()) {
|
|
119
|
-
applyReducedMotion(currentStage, callbacks, complete)
|
|
110
|
+
applyReducedMotion(currentStage, props.callbacks, complete)
|
|
120
111
|
return
|
|
121
112
|
}
|
|
122
113
|
|
|
123
114
|
if (currentStage === 'entering') {
|
|
124
|
-
callbacks.onEnter?.()
|
|
125
|
-
const frameId = applyEnter(el, config)
|
|
115
|
+
props.callbacks.onEnter?.()
|
|
116
|
+
const frameId = applyEnter(el, props.config)
|
|
126
117
|
return () => cancelAnimationFrame(frameId)
|
|
127
118
|
}
|
|
128
119
|
|
|
129
120
|
if (currentStage === 'leaving') {
|
|
130
|
-
callbacks.onLeave?.()
|
|
131
|
-
const frameId = applyLeave(el, config)
|
|
121
|
+
props.callbacks.onLeave?.()
|
|
122
|
+
const frameId = applyLeave(el, props.config)
|
|
132
123
|
return () => cancelAnimationFrame(frameId)
|
|
133
124
|
}
|
|
134
125
|
|
|
135
126
|
if (currentStage === 'entered') {
|
|
136
|
-
removeClasses(el, config.enter)
|
|
127
|
+
removeClasses(el, props.config.enter)
|
|
137
128
|
el.style.transition = ''
|
|
138
129
|
}
|
|
139
130
|
},
|
|
@@ -147,20 +138,20 @@ const TransitionRenderer = ({
|
|
|
147
138
|
effectiveUnmount
|
|
148
139
|
? null
|
|
149
140
|
: h(
|
|
150
|
-
config.tag,
|
|
141
|
+
props.config.tag,
|
|
151
142
|
{
|
|
152
143
|
ref: mergedRef,
|
|
153
|
-
...htmlProps,
|
|
144
|
+
...props.htmlProps,
|
|
154
145
|
style: {
|
|
155
|
-
...((htmlProps.style as CSSProperties) ?? {}),
|
|
146
|
+
...((props.htmlProps.style as CSSProperties) ?? {}),
|
|
156
147
|
display: 'none',
|
|
157
148
|
},
|
|
158
149
|
},
|
|
159
|
-
children,
|
|
150
|
+
props.children,
|
|
160
151
|
)
|
|
161
152
|
}
|
|
162
153
|
>
|
|
163
|
-
{h(config.tag, { ref: mergedRef, ...htmlProps }, children)}
|
|
154
|
+
{h(props.config.tag, { ref: mergedRef, ...props.htmlProps }, props.children)}
|
|
164
155
|
</Show>
|
|
165
156
|
)
|
|
166
157
|
}
|
package/src/utils.ts
CHANGED
|
@@ -28,12 +28,16 @@ export const removeClasses = (el: HTMLElement, classes: string | undefined) => {
|
|
|
28
28
|
/**
|
|
29
29
|
* Executes callback after two animation frames (double-rAF).
|
|
30
30
|
* Ensures the browser paints the current state before applying changes,
|
|
31
|
-
* which is required for CSS transitions to trigger.
|
|
31
|
+
* which is required for CSS transitions to trigger. Returns 0 on SSR —
|
|
32
|
+
* the typeof-window guard makes the SSR-safety contract explicit (callers
|
|
33
|
+
* are always browser-only via `onMount`, but the rule can't AST-trace it).
|
|
32
34
|
*/
|
|
33
|
-
export const nextFrame = (callback: () => void): number =>
|
|
34
|
-
requestAnimationFrame
|
|
35
|
+
export const nextFrame = (callback: () => void): number => {
|
|
36
|
+
if (typeof requestAnimationFrame === 'undefined') return 0
|
|
37
|
+
return requestAnimationFrame(() => {
|
|
35
38
|
requestAnimationFrame(callback)
|
|
36
39
|
})
|
|
40
|
+
}
|
|
37
41
|
|
|
38
42
|
/** Merges two className strings, filtering undefined/empty. */
|
|
39
43
|
export const mergeClassNames = (
|