@pyreon/kinetic 0.11.4 → 0.11.6

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.
@@ -1,116 +1,116 @@
1
- import { describe, expect, it } from "vitest"
2
- import type { CSSProperties } from "../types"
3
- import { addClasses, mergeClassNames, mergeStyles, nextFrame, removeClasses } from "../utils"
1
+ import { describe, expect, it } from 'vitest'
2
+ import type { CSSProperties } from '../types'
3
+ import { addClasses, mergeClassNames, mergeStyles, nextFrame, removeClasses } from '../utils'
4
4
 
5
- describe("mergeClassNames", () => {
6
- it("merges two class strings", () => {
7
- expect(mergeClassNames("a", "b")).toBe("a b")
5
+ describe('mergeClassNames', () => {
6
+ it('merges two class strings', () => {
7
+ expect(mergeClassNames('a', 'b')).toBe('a b')
8
8
  })
9
9
 
10
- it("returns existing when additional is undefined", () => {
11
- expect(mergeClassNames("a", undefined)).toBe("a")
10
+ it('returns existing when additional is undefined', () => {
11
+ expect(mergeClassNames('a', undefined)).toBe('a')
12
12
  })
13
13
 
14
- it("returns additional when existing is undefined", () => {
15
- expect(mergeClassNames(undefined, "b")).toBe("b")
14
+ it('returns additional when existing is undefined', () => {
15
+ expect(mergeClassNames(undefined, 'b')).toBe('b')
16
16
  })
17
17
 
18
- it("returns undefined when both are undefined", () => {
18
+ it('returns undefined when both are undefined', () => {
19
19
  expect(mergeClassNames(undefined, undefined)).toBeUndefined()
20
20
  })
21
21
 
22
- it("returns undefined when both are empty strings", () => {
23
- expect(mergeClassNames("", "")).toBeUndefined()
22
+ it('returns undefined when both are empty strings', () => {
23
+ expect(mergeClassNames('', '')).toBeUndefined()
24
24
  })
25
25
 
26
- it("filters out empty strings", () => {
27
- expect(mergeClassNames("a", "")).toBe("a")
26
+ it('filters out empty strings', () => {
27
+ expect(mergeClassNames('a', '')).toBe('a')
28
28
  })
29
29
  })
30
30
 
31
- describe("mergeStyles", () => {
32
- it("merges two style objects with b taking precedence", () => {
33
- const a = { color: "red", fontSize: "12px" } as CSSProperties
34
- const b = { color: "blue" } as CSSProperties
35
- expect(mergeStyles(a, b)).toEqual({ color: "blue", fontSize: "12px" })
31
+ describe('mergeStyles', () => {
32
+ it('merges two style objects with b taking precedence', () => {
33
+ const a = { color: 'red', fontSize: '12px' } as CSSProperties
34
+ const b = { color: 'blue' } as CSSProperties
35
+ expect(mergeStyles(a, b)).toEqual({ color: 'blue', fontSize: '12px' })
36
36
  })
37
37
 
38
- it("returns undefined when both are undefined", () => {
38
+ it('returns undefined when both are undefined', () => {
39
39
  expect(mergeStyles(undefined, undefined)).toBeUndefined()
40
40
  })
41
41
 
42
- it("returns b when a is undefined", () => {
43
- const b = { color: "blue" } as CSSProperties
42
+ it('returns b when a is undefined', () => {
43
+ const b = { color: 'blue' } as CSSProperties
44
44
  expect(mergeStyles(undefined, b)).toBe(b)
45
45
  })
46
46
 
47
- it("returns a when b is undefined", () => {
48
- const a = { color: "red" } as CSSProperties
47
+ it('returns a when b is undefined', () => {
48
+ const a = { color: 'red' } as CSSProperties
49
49
  expect(mergeStyles(a, undefined)).toBe(a)
50
50
  })
51
51
  })
52
52
 
53
- describe("addClasses", () => {
54
- it("adds space-separated classes to an element", () => {
55
- const el = document.createElement("div")
56
- addClasses(el, "foo bar")
57
- expect(el.classList.contains("foo")).toBe(true)
58
- expect(el.classList.contains("bar")).toBe(true)
53
+ describe('addClasses', () => {
54
+ it('adds space-separated classes to an element', () => {
55
+ const el = document.createElement('div')
56
+ addClasses(el, 'foo bar')
57
+ expect(el.classList.contains('foo')).toBe(true)
58
+ expect(el.classList.contains('bar')).toBe(true)
59
59
  })
60
60
 
61
- it("does nothing when classes is undefined", () => {
62
- const el = document.createElement("div")
61
+ it('does nothing when classes is undefined', () => {
62
+ const el = document.createElement('div')
63
63
  addClasses(el, undefined)
64
64
  expect(el.classList.length).toBe(0)
65
65
  })
66
66
 
67
- it("does nothing when classes is empty string", () => {
68
- const el = document.createElement("div")
69
- addClasses(el, "")
67
+ it('does nothing when classes is empty string', () => {
68
+ const el = document.createElement('div')
69
+ addClasses(el, '')
70
70
  expect(el.classList.length).toBe(0)
71
71
  })
72
72
 
73
- it("does nothing when classes is whitespace-only", () => {
74
- const el = document.createElement("div")
75
- addClasses(el, " ")
73
+ it('does nothing when classes is whitespace-only', () => {
74
+ const el = document.createElement('div')
75
+ addClasses(el, ' ')
76
76
  expect(el.classList.length).toBe(0)
77
77
  })
78
78
  })
79
79
 
80
- describe("removeClasses", () => {
81
- it("removes space-separated classes from an element", () => {
82
- const el = document.createElement("div")
83
- el.classList.add("foo", "bar", "baz")
84
- removeClasses(el, "foo bar")
85
- expect(el.classList.contains("foo")).toBe(false)
86
- expect(el.classList.contains("bar")).toBe(false)
87
- expect(el.classList.contains("baz")).toBe(true)
80
+ describe('removeClasses', () => {
81
+ it('removes space-separated classes from an element', () => {
82
+ const el = document.createElement('div')
83
+ el.classList.add('foo', 'bar', 'baz')
84
+ removeClasses(el, 'foo bar')
85
+ expect(el.classList.contains('foo')).toBe(false)
86
+ expect(el.classList.contains('bar')).toBe(false)
87
+ expect(el.classList.contains('baz')).toBe(true)
88
88
  })
89
89
 
90
- it("does nothing when classes is undefined", () => {
91
- const el = document.createElement("div")
92
- el.classList.add("foo")
90
+ it('does nothing when classes is undefined', () => {
91
+ const el = document.createElement('div')
92
+ el.classList.add('foo')
93
93
  removeClasses(el, undefined)
94
- expect(el.classList.contains("foo")).toBe(true)
94
+ expect(el.classList.contains('foo')).toBe(true)
95
95
  })
96
96
 
97
- it("does nothing when classes is empty string", () => {
98
- const el = document.createElement("div")
99
- el.classList.add("foo")
100
- removeClasses(el, "")
101
- expect(el.classList.contains("foo")).toBe(true)
97
+ it('does nothing when classes is empty string', () => {
98
+ const el = document.createElement('div')
99
+ el.classList.add('foo')
100
+ removeClasses(el, '')
101
+ expect(el.classList.contains('foo')).toBe(true)
102
102
  })
103
103
 
104
- it("does nothing when classes is whitespace-only", () => {
105
- const el = document.createElement("div")
106
- el.classList.add("foo")
107
- removeClasses(el, " ")
108
- expect(el.classList.contains("foo")).toBe(true)
104
+ it('does nothing when classes is whitespace-only', () => {
105
+ const el = document.createElement('div')
106
+ el.classList.add('foo')
107
+ removeClasses(el, ' ')
108
+ expect(el.classList.contains('foo')).toBe(true)
109
109
  })
110
110
  })
111
111
 
112
- describe("nextFrame", () => {
113
- it("calls callback after double rAF", () => {
112
+ describe('nextFrame', () => {
113
+ it('calls callback after double rAF', () => {
114
114
  const callbacks: (() => void)[] = []
115
115
  const originalRaf = globalThis.requestAnimationFrame
116
116
  globalThis.requestAnimationFrame = ((cb: () => void) => {
package/src/index.ts CHANGED
@@ -1,23 +1,15 @@
1
- export { default as kinetic } from "./kinetic"
2
- export type { KineticComponent } from "./kinetic/types"
3
- export type { Preset } from "./presets"
4
- export {
5
- fade,
6
- presets,
7
- scaleIn,
8
- slideDown,
9
- slideLeft,
10
- slideRight,
11
- slideUp,
12
- } from "./presets"
1
+ export { default as kinetic } from './kinetic'
2
+ export type { KineticComponent } from './kinetic/types'
3
+ export type { Preset } from './presets'
4
+ export { fade, presets, scaleIn, slideDown, slideLeft, slideRight, slideUp } from './presets'
13
5
  export type {
14
6
  ClassTransitionProps,
15
7
  StyleTransitionProps,
16
8
  TransitionCallbacks,
17
9
  TransitionStage,
18
10
  TransitionStateResult,
19
- } from "./types"
20
- export type { UseAnimationEnd } from "./useAnimationEnd"
21
- export { default as useAnimationEnd } from "./useAnimationEnd"
22
- export type { UseTransitionState } from "./useTransitionState"
23
- export { default as useTransitionState } from "./useTransitionState"
11
+ } from './types'
12
+ export type { UseAnimationEnd } from './useAnimationEnd'
13
+ export { default as useAnimationEnd } from './useAnimationEnd'
14
+ export type { UseTransitionState } from './useTransitionState'
15
+ export { default as useTransitionState } from './useTransitionState'
@@ -1,10 +1,10 @@
1
- import type { VNode } from "@pyreon/core"
2
- import { createRef, h, Show } from "@pyreon/core"
3
- import { runUntracked, signal, watch } from "@pyreon/reactivity"
4
- import type { CSSProperties, TransitionCallbacks, TransitionStage } from "../types"
5
- import useAnimationEnd from "../useAnimationEnd"
6
- import { useReducedMotion } from "../useReducedMotion"
7
- import type { KineticConfig } from "./types"
1
+ import type { VNode } from '@pyreon/core'
2
+ import { createRef, h, Show } from '@pyreon/core'
3
+ import { runUntracked, signal, watch } from '@pyreon/reactivity'
4
+ import type { CSSProperties, TransitionCallbacks, TransitionStage } from '../types'
5
+ import useAnimationEnd from '../useAnimationEnd'
6
+ import { useReducedMotion } from '../useReducedMotion'
7
+ import type { KineticConfig } from './types'
8
8
 
9
9
  type CollapseRendererProps = {
10
10
  config: KineticConfig
@@ -38,11 +38,11 @@ const CollapseRenderer = ({
38
38
 
39
39
  const effectiveAppear = appear ?? config.appear ?? false
40
40
  const effectiveTimeout = timeout ?? config.timeout ?? 5000
41
- const effectiveTransition = transition ?? config.transition ?? "height 300ms ease"
41
+ const effectiveTransition = transition ?? config.transition ?? 'height 300ms ease'
42
42
 
43
43
  const initialShow = show()
44
44
  const needsAppear = effectiveAppear && initialShow
45
- const stage = signal<TransitionStage>(initialShow ? "entered" : "hidden")
45
+ const stage = signal<TransitionStage>(initialShow ? 'entered' : 'hidden')
46
46
  let isInitialMount = true
47
47
  let appearTriggered = false
48
48
 
@@ -50,7 +50,7 @@ const CollapseRenderer = ({
50
50
  if (needsAppear) {
51
51
  const orig = wrapperRef
52
52
  const proxy = { current: null as HTMLElement | null }
53
- Object.defineProperty(proxy, "current", {
53
+ Object.defineProperty(proxy, 'current', {
54
54
  get() {
55
55
  return orig.current
56
56
  },
@@ -58,7 +58,7 @@ const CollapseRenderer = ({
58
58
  orig.current = node
59
59
  if (node && !appearTriggered) {
60
60
  appearTriggered = true
61
- queueMicrotask(() => stage.set("entering"))
61
+ queueMicrotask(() => stage.set('entering'))
62
62
  }
63
63
  },
64
64
  })
@@ -76,10 +76,10 @@ const CollapseRenderer = ({
76
76
  }
77
77
 
78
78
  const currentStage = runUntracked(() => stage())
79
- if (showVal && (currentStage === "hidden" || currentStage === "leaving")) {
80
- stage.set("entering")
81
- } else if (!showVal && (currentStage === "entered" || currentStage === "entering")) {
82
- stage.set("leaving")
79
+ if (showVal && (currentStage === 'hidden' || currentStage === 'leaving')) {
80
+ stage.set('entering')
81
+ } else if (!showVal && (currentStage === 'entered' || currentStage === 'entering')) {
82
+ stage.set('leaving')
83
83
  }
84
84
  },
85
85
  { immediate: true },
@@ -94,44 +94,44 @@ const CollapseRenderer = ({
94
94
  if (!wrapper || !content) return
95
95
 
96
96
  if (reducedMotion()) {
97
- if (currentStage === "entering") {
97
+ if (currentStage === 'entering') {
98
98
  callbacks.onEnter?.()
99
- wrapper.style.height = "auto"
100
- wrapper.style.overflow = ""
99
+ wrapper.style.height = 'auto'
100
+ wrapper.style.overflow = ''
101
101
  callbacks.onAfterEnter?.()
102
- stage.set("entered")
103
- } else if (currentStage === "leaving") {
102
+ stage.set('entered')
103
+ } else if (currentStage === 'leaving') {
104
104
  callbacks.onLeave?.()
105
- wrapper.style.height = "0px"
106
- wrapper.style.overflow = "hidden"
105
+ wrapper.style.height = '0px'
106
+ wrapper.style.overflow = 'hidden'
107
107
  callbacks.onAfterLeave?.()
108
- stage.set("hidden")
108
+ stage.set('hidden')
109
109
  }
110
110
  return
111
111
  }
112
112
 
113
- if (currentStage === "entering") {
113
+ if (currentStage === 'entering') {
114
114
  callbacks.onEnter?.()
115
115
  const height = content.scrollHeight
116
- wrapper.style.transition = "none"
117
- wrapper.style.height = "0px"
118
- wrapper.style.overflow = "hidden"
116
+ wrapper.style.transition = 'none'
117
+ wrapper.style.height = '0px'
118
+ wrapper.style.overflow = 'hidden'
119
119
  // Force reflow
120
120
  void wrapper.offsetHeight
121
121
  wrapper.style.transition = effectiveTransition
122
122
  wrapper.style.height = `${height}px`
123
123
  }
124
124
 
125
- if (currentStage === "leaving") {
125
+ if (currentStage === 'leaving') {
126
126
  callbacks.onLeave?.()
127
127
  const height = content.scrollHeight
128
- wrapper.style.transition = "none"
128
+ wrapper.style.transition = 'none'
129
129
  wrapper.style.height = `${height}px`
130
- wrapper.style.overflow = "hidden"
130
+ wrapper.style.overflow = 'hidden'
131
131
  // Force reflow
132
132
  void wrapper.offsetHeight
133
133
  wrapper.style.transition = effectiveTransition
134
- wrapper.style.height = "0px"
134
+ wrapper.style.height = '0px'
135
135
  }
136
136
  },
137
137
  { immediate: true },
@@ -139,31 +139,31 @@ const CollapseRenderer = ({
139
139
 
140
140
  useAnimationEnd({
141
141
  ref: wrapperRef,
142
- active: () => (stage() === "entering" || stage() === "leaving") && !reducedMotion(),
142
+ active: () => (stage() === 'entering' || stage() === 'leaving') && !reducedMotion(),
143
143
  timeout: effectiveTimeout,
144
144
  onEnd: () => {
145
145
  const wrapper = wrapperRef.current
146
- if (stage() === "entering") {
146
+ if (stage() === 'entering') {
147
147
  if (wrapper) {
148
- wrapper.style.height = "auto"
149
- wrapper.style.overflow = ""
150
- wrapper.style.transition = ""
148
+ wrapper.style.height = 'auto'
149
+ wrapper.style.overflow = ''
150
+ wrapper.style.transition = ''
151
151
  }
152
152
  callbacks.onAfterEnter?.()
153
- stage.set("entered")
154
- } else if (stage() === "leaving") {
153
+ stage.set('entered')
154
+ } else if (stage() === 'leaving') {
155
155
  callbacks.onAfterLeave?.()
156
- stage.set("hidden")
156
+ stage.set('hidden')
157
157
  }
158
158
  },
159
159
  })
160
160
 
161
- const shouldRender = () => stage() !== "hidden"
161
+ const shouldRender = () => stage() !== 'hidden'
162
162
 
163
163
  const wrapperStyle: CSSProperties = {
164
164
  ...((htmlProps.style as CSSProperties) ?? {}),
165
- ...(stage() !== "entered" ? { overflow: "hidden" } : {}),
166
- ...(stage() === "hidden" ? { height: "0px" } : stage() === "entered" ? { height: "auto" } : {}),
165
+ ...(stage() !== 'entered' ? { overflow: 'hidden' } : {}),
166
+ ...(stage() === 'hidden' ? { height: '0px' } : stage() === 'entered' ? { height: 'auto' } : {}),
167
167
  }
168
168
 
169
169
  return h(
@@ -1,9 +1,9 @@
1
- import type { VNode } from "@pyreon/core"
2
- import { h } from "@pyreon/core"
3
- import { signal } from "@pyreon/reactivity"
4
- import type { TransitionCallbacks } from "../types"
5
- import TransitionItem from "./TransitionItem"
6
- import type { KineticConfig } from "./types"
1
+ import type { VNode } from '@pyreon/core'
2
+ import { h } from '@pyreon/core'
3
+ import { signal } from '@pyreon/reactivity'
4
+ import type { TransitionCallbacks } from '../types'
5
+ import TransitionItem from './TransitionItem'
6
+ import type { KineticConfig } from './types'
7
7
 
8
8
  type GroupRendererProps = {
9
9
  config: KineticConfig
@@ -11,13 +11,18 @@ type GroupRendererProps = {
11
11
  appear?: boolean | undefined
12
12
  timeout?: number | undefined
13
13
  callbacks: Partial<TransitionCallbacks>
14
- children: VNode[]
14
+ /**
15
+ * Children can be a static array OR a reactive accessor `() => VNode[]`.
16
+ * When passed as an accessor, GroupRenderer tracks changes and
17
+ * animates entering/leaving children automatically.
18
+ */
19
+ children: VNode[] | (() => VNode[])
15
20
  }
16
21
 
17
22
  type KeyedChild = { key: string | number; element: VNode }
18
23
 
19
24
  const isVNode = (child: unknown): child is VNode =>
20
- child != null && typeof child === "object" && "type" in (child as object)
25
+ child != null && typeof child === 'object' && 'type' in (child as object)
21
26
 
22
27
  const getKeyedChildren = (children: VNode[]): KeyedChild[] => {
23
28
  const result: KeyedChild[] = []
@@ -37,6 +42,9 @@ const getKeyedChildren = (children: VNode[]): KeyedChild[] => {
37
42
  * Children that appear (new key) animate in. Children that disappear
38
43
  * (removed key) stay in DOM during leave animation, then unmount.
39
44
  * config.tag wraps all children as a container element.
45
+ *
46
+ * In Pyreon, components run once. Pass children as a reactive accessor
47
+ * `() => VNode[]` for the group to detect changes and animate entries/exits.
40
48
  */
41
49
  const GroupRenderer = ({
42
50
  config,
@@ -51,74 +59,89 @@ const GroupRenderer = ({
51
59
 
52
60
  const prevMap = new Map<string | number, VNode>()
53
61
  const leavingMap = new Map<string | number, VNode>()
54
- const forceUpdateSignal = signal(0)
62
+ const forceUpdate = signal(0)
55
63
 
56
- const currentKeyed = getKeyedChildren(children)
57
- const currentMap = new Map<string | number, VNode>()
58
- for (const { key, element } of currentKeyed) {
59
- currentMap.set(key, element)
60
- }
61
-
62
- const initialKeys: Set<string | number> = new Set(currentMap.keys())
63
-
64
- // Detect leaving children
65
- for (const [key, child] of prevMap) {
66
- if (!currentMap.has(key)) {
67
- leavingMap.set(key, child)
68
- }
69
- }
64
+ // Normalize children to an accessor
65
+ const getChildren = typeof children === 'function' ? (children as () => VNode[]) : () => children
70
66
 
71
- // If a leaving child reappears, stop leaving
72
- for (const key of currentMap.keys()) {
73
- leavingMap.delete(key)
74
- }
75
-
76
- prevMap.clear()
77
- for (const [key, element] of currentMap) {
67
+ // Track initial keys for appear animation logic
68
+ const initialKeyed = getKeyedChildren(getChildren())
69
+ const initialKeys = new Set(initialKeyed.map((c) => c.key))
70
+ for (const { key, element } of initialKeyed) {
78
71
  prevMap.set(key, element)
79
72
  }
80
73
 
81
74
  const handleAfterLeave = (key: string | number) => {
82
75
  leavingMap.delete(key)
83
76
  callbacks.onAfterLeave?.()
84
- forceUpdateSignal.update((c) => c + 1)
77
+ forceUpdate.update((c) => c + 1)
85
78
  }
86
79
 
87
- // Merge current + leaving
88
- const allEntries: KeyedChild[] = [...currentKeyed]
89
- for (const [key, element] of leavingMap) {
90
- allEntries.push({ key, element })
91
- }
80
+ // Reactive accessor re-evaluates when children() or forceUpdate changes
81
+ return (() => {
82
+ forceUpdate()
83
+
84
+ const currentChildren = getChildren()
85
+ const currentKeyed = getKeyedChildren(currentChildren)
86
+ const currentMap = new Map<string | number, VNode>()
87
+ for (const { key, element } of currentKeyed) {
88
+ currentMap.set(key, element)
89
+ }
90
+
91
+ // Detect leaving children
92
+ for (const [key, child] of prevMap) {
93
+ if (!currentMap.has(key) && !leavingMap.has(key)) {
94
+ leavingMap.set(key, child)
95
+ }
96
+ }
97
+
98
+ // Cancel leave if child reappears
99
+ for (const key of currentMap.keys()) {
100
+ leavingMap.delete(key)
101
+ }
102
+
103
+ // Update prev for next diff
104
+ prevMap.clear()
105
+ for (const [key, element] of currentMap) {
106
+ prevMap.set(key, element)
107
+ }
108
+
109
+ // Merge current + leaving
110
+ const allEntries: KeyedChild[] = [...currentKeyed]
111
+ for (const [key, element] of leavingMap) {
112
+ allEntries.push({ key, element })
113
+ }
92
114
 
93
- const groupedChildren = allEntries.map(({ key, element }) => {
94
- const isInitial = initialKeys.has(key)
95
- const isShowing = currentMap.has(key)
96
-
97
- return (
98
- <TransitionItem
99
- show={() => isShowing}
100
- appear={isInitial ? effectiveAppear : true}
101
- timeout={effectiveTimeout}
102
- enterStyle={config.enterStyle}
103
- enterToStyle={config.enterToStyle}
104
- enterTransition={config.enterTransition}
105
- leaveStyle={config.leaveStyle}
106
- leaveToStyle={config.leaveToStyle}
107
- leaveTransition={config.leaveTransition}
108
- enter={config.enter}
109
- enterFrom={config.enterFrom}
110
- enterTo={config.enterTo}
111
- leave={config.leave}
112
- leaveFrom={config.leaveFrom}
113
- leaveTo={config.leaveTo}
114
- onAfterLeave={() => handleAfterLeave(key)}
115
- >
116
- {element}
117
- </TransitionItem>
118
- )
119
- })
120
-
121
- return h(config.tag, { ...htmlProps }, ...groupedChildren)
115
+ const groupedChildren = allEntries.map(({ key, element }) => {
116
+ const isInitial = initialKeys.has(key)
117
+ const isShowing = currentMap.has(key)
118
+
119
+ return (
120
+ <TransitionItem
121
+ show={() => isShowing}
122
+ appear={isInitial ? effectiveAppear : true}
123
+ timeout={effectiveTimeout}
124
+ enterStyle={config.enterStyle}
125
+ enterToStyle={config.enterToStyle}
126
+ enterTransition={config.enterTransition}
127
+ leaveStyle={config.leaveStyle}
128
+ leaveToStyle={config.leaveToStyle}
129
+ leaveTransition={config.leaveTransition}
130
+ enter={config.enter}
131
+ enterFrom={config.enterFrom}
132
+ enterTo={config.enterTo}
133
+ leave={config.leave}
134
+ leaveFrom={config.leaveFrom}
135
+ leaveTo={config.leaveTo}
136
+ onAfterLeave={() => handleAfterLeave(key)}
137
+ >
138
+ {element}
139
+ </TransitionItem>
140
+ )
141
+ })
142
+
143
+ return h(config.tag, { ...htmlProps }, ...groupedChildren)
144
+ }) as unknown as VNode
122
145
  }
123
146
 
124
147
  export default GroupRenderer
@@ -1,9 +1,9 @@
1
- import type { VNode } from "@pyreon/core"
2
- import { h } from "@pyreon/core"
3
- import type { CSSProperties, TransitionCallbacks } from "../types"
4
- import { cloneVNode } from "../utils"
5
- import TransitionItem from "./TransitionItem"
6
- import type { KineticConfig } from "./types"
1
+ import type { VNode } from '@pyreon/core'
2
+ import { h } from '@pyreon/core'
3
+ import type { CSSProperties, TransitionCallbacks } from '../types'
4
+ import { cloneVNode } from '../utils'
5
+ import TransitionItem from './TransitionItem'
6
+ import type { KineticConfig } from './types'
7
7
 
8
8
  type StaggerRendererProps = {
9
9
  config: KineticConfig
@@ -18,7 +18,7 @@ type StaggerRendererProps = {
18
18
  }
19
19
 
20
20
  const isVNode = (child: unknown): child is VNode =>
21
- child != null && typeof child === "object" && "type" in (child as object)
21
+ child != null && typeof child === 'object' && 'type' in (child as object)
22
22
 
23
23
  /**
24
24
  * Renders children with staggered enter/exit animation.
@@ -73,8 +73,8 @@ const StaggerRenderer = ({
73
73
  {cloneVNode(child, {
74
74
  style: {
75
75
  ...((child.props as Record<string, unknown>)?.style as CSSProperties | undefined),
76
- "--stagger-index": staggerIndex,
77
- "--stagger-interval": `${effectiveInterval}ms`,
76
+ '--stagger-index': staggerIndex,
77
+ '--stagger-interval': `${effectiveInterval}ms`,
78
78
  transitionDelay: `${delay}ms`,
79
79
  } as CSSProperties,
80
80
  })}