@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,20 +1,25 @@
1
- import type { VNode } from "@pyreon/core"
2
- import { signal } from "@pyreon/reactivity"
3
- import Transition from "./Transition"
4
- import type { ClassTransitionProps, StyleTransitionProps, TransitionCallbacks } from "./types"
1
+ import type { VNode } from '@pyreon/core'
2
+ import { signal } from '@pyreon/reactivity'
3
+ import Transition from './Transition'
4
+ import type { ClassTransitionProps, StyleTransitionProps, TransitionCallbacks } from './types'
5
5
 
6
6
  export type TransitionGroupProps = ClassTransitionProps &
7
7
  StyleTransitionProps &
8
8
  TransitionCallbacks & {
9
9
  appear?: boolean | undefined
10
10
  timeout?: number | undefined
11
- children: VNode[]
11
+ /**
12
+ * Children can be a static array OR a reactive accessor `() => VNode[]`.
13
+ * When passed as an accessor, TransitionGroup tracks changes and
14
+ * animates entering/leaving children automatically.
15
+ */
16
+ children: VNode[] | (() => VNode[])
12
17
  }
13
18
 
14
19
  type KeyedChild = { key: string | number; element: VNode }
15
20
 
16
21
  const isVNode = (child: unknown): child is VNode =>
17
- child != null && typeof child === "object" && "type" in (child as object)
22
+ child != null && typeof child === 'object' && 'type' in (child as object)
18
23
 
19
24
  const getKeyedChildren = (children: VNode[]): KeyedChild[] => {
20
25
  const result: KeyedChild[] = []
@@ -29,6 +34,14 @@ const getKeyedChildren = (children: VNode[]): KeyedChild[] => {
29
34
  return result
30
35
  }
31
36
 
37
+ /**
38
+ * Renders children with key-based enter/exit animations.
39
+ *
40
+ * In Pyreon, components run once. For TransitionGroup to detect children
41
+ * changes, pass children as a reactive accessor: `() => VNode[]`.
42
+ * The component uses a reactive accessor internally to diff previous vs
43
+ * current children and animate entries/exits.
44
+ */
32
45
  const TransitionGroup = ({
33
46
  children,
34
47
  appear = false,
@@ -38,71 +51,84 @@ const TransitionGroup = ({
38
51
  }: TransitionGroupProps): VNode | null => {
39
52
  const prevMap = new Map<string | number, VNode>()
40
53
  const leavingMap = new Map<string | number, VNode>()
41
- const forceUpdateSignal = signal(0)
54
+ const forceUpdate = signal(0)
42
55
 
43
- // Build current keyed children map
44
- const currentKeyed = getKeyedChildren(children)
45
- const currentMap = new Map<string | number, VNode>()
46
- for (const { key, element } of currentKeyed) {
47
- currentMap.set(key, element)
48
- }
49
-
50
- // Track initial keys to know which children were present on first render
51
- const initialKeys: Set<string | number> = new Set(currentMap.keys())
52
-
53
- // Detect leaving children (were in prev but not in current)
54
- for (const [key, child] of prevMap) {
55
- if (!currentMap.has(key)) {
56
- leavingMap.set(key, child)
57
- }
58
- }
56
+ // Normalize children to an accessor for uniform handling
57
+ const getChildren = typeof children === 'function' ? (children as () => VNode[]) : () => children
59
58
 
60
- // If a leaving child reappears, stop leaving
61
- for (const key of currentMap.keys()) {
62
- leavingMap.delete(key)
63
- }
64
-
65
- // Update prev
66
- prevMap.clear()
67
- for (const [key, element] of currentMap) {
59
+ // Track initial keys for appear animation logic
60
+ const initialKeyed = getKeyedChildren(getChildren())
61
+ const initialKeys = new Set(initialKeyed.map((c) => c.key))
62
+ for (const { key, element } of initialKeyed) {
68
63
  prevMap.set(key, element)
69
64
  }
70
65
 
71
66
  const handleAfterLeave = (key: string | number) => {
72
67
  leavingMap.delete(key)
73
68
  onAfterLeave?.()
74
- forceUpdateSignal.update((c) => c + 1)
69
+ forceUpdate.update((c) => c + 1)
75
70
  }
76
71
 
77
- // Merge current + leaving, preserving insertion order
78
- const allEntries: KeyedChild[] = [...currentKeyed]
72
+ // Reactive accessor re-evaluates when children() or forceUpdate changes.
73
+ // The runtime mounts this via mountReactive + effect, creating a
74
+ // reactive scope that tracks signal reads.
75
+ return (() => {
76
+ // Read forceUpdate to re-evaluate when leaving children finish
77
+ forceUpdate()
78
+
79
+ const currentChildren = getChildren()
80
+ const currentKeyed = getKeyedChildren(currentChildren)
81
+ const currentMap = new Map<string | number, VNode>()
82
+ for (const { key, element } of currentKeyed) {
83
+ currentMap.set(key, element)
84
+ }
85
+
86
+ // Detect leaving children (were in prev but not in current)
87
+ for (const [key, child] of prevMap) {
88
+ if (!currentMap.has(key) && !leavingMap.has(key)) {
89
+ leavingMap.set(key, child)
90
+ }
91
+ }
79
92
 
80
- for (const [key, element] of leavingMap) {
81
- allEntries.push({ key, element })
82
- }
93
+ // If a leaving child reappears, cancel the leave
94
+ for (const key of currentMap.keys()) {
95
+ leavingMap.delete(key)
96
+ }
97
+
98
+ // Update prev for next diff
99
+ prevMap.clear()
100
+ for (const [key, element] of currentMap) {
101
+ prevMap.set(key, element)
102
+ }
103
+
104
+ // Merge current + leaving, preserving insertion order
105
+ const allEntries: KeyedChild[] = [...currentKeyed]
106
+ for (const [key, element] of leavingMap) {
107
+ allEntries.push({ key, element })
108
+ }
83
109
 
84
- return (
85
- <>
86
- {allEntries.map(({ key, element }) => {
87
- // New children (not in initial render) must appear with animation
88
- const isInitial = initialKeys.has(key)
89
- const isShowing = currentMap.has(key)
90
-
91
- return (
92
- <Transition
93
- key={key}
94
- show={() => isShowing}
95
- appear={isInitial ? appear : true}
96
- timeout={timeout}
97
- {...transitionProps}
98
- onAfterLeave={() => handleAfterLeave(key)}
99
- >
100
- {element}
101
- </Transition>
102
- )
103
- })}
104
- </>
105
- )
110
+ return (
111
+ <>
112
+ {allEntries.map(({ key, element }) => {
113
+ const isInitial = initialKeys.has(key)
114
+ const isShowing = currentMap.has(key)
115
+
116
+ return (
117
+ <Transition
118
+ key={key}
119
+ show={() => isShowing}
120
+ appear={isInitial ? appear : true}
121
+ timeout={timeout}
122
+ {...transitionProps}
123
+ onAfterLeave={() => handleAfterLeave(key)}
124
+ >
125
+ {element}
126
+ </Transition>
127
+ )
128
+ })}
129
+ </>
130
+ )
131
+ }) as unknown as VNode
106
132
  }
107
133
 
108
134
  export default TransitionGroup