@pyreon/kinetic 0.11.5 → 0.11.7
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/README.md +27 -24
- package/lib/index.d.ts +8 -8
- package/package.json +22 -22
- package/src/Collapse.tsx +44 -44
- package/src/Stagger.tsx +7 -7
- package/src/Transition.tsx +18 -18
- package/src/TransitionGroup.tsx +6 -6
- package/src/__tests__/Collapse.test.tsx +125 -125
- package/src/__tests__/GroupRenderer.test.tsx +78 -78
- package/src/__tests__/StaggerRenderer.test.tsx +99 -99
- package/src/__tests__/Transition.test.tsx +89 -89
- package/src/__tests__/TransitionItem.test.tsx +120 -120
- package/src/__tests__/kinetic.test.tsx +135 -135
- package/src/__tests__/presets.test.ts +15 -15
- package/src/__tests__/useAnimationEnd.test.ts +33 -33
- package/src/__tests__/useReducedMotion.test.ts +22 -22
- package/src/__tests__/useTransitionState.test.ts +38 -38
- package/src/__tests__/utils.test.ts +63 -63
- package/src/index.ts +9 -17
- package/src/kinetic/CollapseRenderer.tsx +42 -42
- package/src/kinetic/GroupRenderer.tsx +8 -8
- package/src/kinetic/StaggerRenderer.tsx +9 -9
- package/src/kinetic/TransitionItem.tsx +18 -18
- package/src/kinetic/TransitionRenderer.tsx +19 -19
- package/src/kinetic/createKineticComponent.tsx +27 -27
- package/src/kinetic/types.ts +13 -13
- package/src/kinetic.ts +4 -4
- package/src/presets.ts +33 -33
- package/src/types.ts +3 -3
- package/src/useAnimationEnd.ts +8 -8
- package/src/useReducedMotion.ts +5 -5
- package/src/useTransitionState.ts +12 -12
- package/src/utils.ts +4 -4
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import type { VNode, VNodeChild } from
|
|
2
|
-
import { signal } from
|
|
1
|
+
import type { VNode, VNodeChild } from '@pyreon/core'
|
|
2
|
+
import { signal } from '@pyreon/reactivity'
|
|
3
3
|
|
|
4
4
|
let _reducedMotion = false
|
|
5
5
|
|
|
6
|
-
vi.mock(
|
|
6
|
+
vi.mock('../useReducedMotion', () => ({
|
|
7
7
|
useReducedMotion: () => () => _reducedMotion,
|
|
8
8
|
}))
|
|
9
9
|
|
|
10
|
-
import { kinetic } from
|
|
11
|
-
import { fade, slideUp } from
|
|
10
|
+
import { kinetic } from '../index'
|
|
11
|
+
import { fade, slideUp } from '../presets'
|
|
12
12
|
|
|
13
13
|
// Mock rAF for deterministic testing
|
|
14
14
|
let rafCallbacks: (() => void)[] = []
|
|
@@ -20,20 +20,20 @@ beforeEach(() => {
|
|
|
20
20
|
rafCallbacks = []
|
|
21
21
|
|
|
22
22
|
vi.stubGlobal(
|
|
23
|
-
|
|
23
|
+
'requestAnimationFrame',
|
|
24
24
|
vi.fn((cb: () => void) => {
|
|
25
25
|
rafCallbacks.push(cb)
|
|
26
26
|
return rafCallbacks.length
|
|
27
27
|
}),
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
vi.stubGlobal(
|
|
30
|
+
vi.stubGlobal('cancelAnimationFrame', vi.fn())
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
afterEach(() => {
|
|
34
34
|
vi.useRealTimers()
|
|
35
|
-
vi.stubGlobal(
|
|
36
|
-
vi.stubGlobal(
|
|
35
|
+
vi.stubGlobal('requestAnimationFrame', originalRaf)
|
|
36
|
+
vi.stubGlobal('cancelAnimationFrame', originalCaf)
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
const flushRaf = () => {
|
|
@@ -43,8 +43,8 @@ const flushRaf = () => {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const fireTransitionEnd = (el: HTMLElement) => {
|
|
46
|
-
const event = new Event(
|
|
47
|
-
Object.defineProperty(event,
|
|
46
|
+
const event = new Event('transitionend', { bubbles: true })
|
|
47
|
+
Object.defineProperty(event, 'target', { value: el })
|
|
48
48
|
el.dispatchEvent(event)
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -59,9 +59,9 @@ const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
|
59
59
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: complex logic is inherent to this function
|
|
60
60
|
const visitNode = (node: VNode) => {
|
|
61
61
|
const props = node.props as Record<string, unknown>
|
|
62
|
-
if (typeof props?.ref ===
|
|
62
|
+
if (typeof props?.ref === 'function') {
|
|
63
63
|
;(props.ref as (element: HTMLElement | null) => void)(el)
|
|
64
|
-
} else if (props?.ref && typeof props.ref ===
|
|
64
|
+
} else if (props?.ref && typeof props.ref === 'object') {
|
|
65
65
|
;(props.ref as { current: HTMLElement | null }).current = el
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -69,7 +69,7 @@ const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
|
69
69
|
if (node.children) {
|
|
70
70
|
const children = Array.isArray(node.children) ? node.children : [node.children]
|
|
71
71
|
for (const child of children) {
|
|
72
|
-
if (child && typeof child ===
|
|
72
|
+
if (child && typeof child === 'object' && 'type' in (child as object)) {
|
|
73
73
|
visitNode(child as VNode)
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -79,7 +79,7 @@ const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
|
79
79
|
if (props?.children) {
|
|
80
80
|
const pChildren = Array.isArray(props.children) ? props.children : [props.children]
|
|
81
81
|
for (const child of pChildren) {
|
|
82
|
-
if (child && typeof child ===
|
|
82
|
+
if (child && typeof child === 'object' && 'type' in (child as object)) {
|
|
83
83
|
visitNode(child as VNode)
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -88,8 +88,8 @@ const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
|
88
88
|
// Visit fallback on Show
|
|
89
89
|
if (
|
|
90
90
|
props?.fallback &&
|
|
91
|
-
typeof props.fallback ===
|
|
92
|
-
|
|
91
|
+
typeof props.fallback === 'object' &&
|
|
92
|
+
'type' in (props.fallback as object)
|
|
93
93
|
) {
|
|
94
94
|
visitNode(props.fallback as VNode)
|
|
95
95
|
}
|
|
@@ -105,8 +105,8 @@ const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
|
105
105
|
* but do NOT recurse into framework components like Show.
|
|
106
106
|
*/
|
|
107
107
|
const resolveComponent = (vnode: VNodeChild): VNode | null => {
|
|
108
|
-
if (!vnode || typeof vnode !==
|
|
109
|
-
if (typeof vnode.type ===
|
|
108
|
+
if (!vnode || typeof vnode !== 'object' || !('type' in vnode)) return null
|
|
109
|
+
if (typeof vnode.type === 'function') {
|
|
110
110
|
const props = vnode.props as Record<string, unknown>
|
|
111
111
|
const children = vnode.children
|
|
112
112
|
// Call the component function with props (children merged in)
|
|
@@ -114,7 +114,7 @@ const resolveComponent = (vnode: VNodeChild): VNode | null => {
|
|
|
114
114
|
...props,
|
|
115
115
|
...(children != null ? { children } : {}),
|
|
116
116
|
})
|
|
117
|
-
if (!result || typeof result !==
|
|
117
|
+
if (!result || typeof result !== 'object' || !('type' in result)) return null
|
|
118
118
|
return result
|
|
119
119
|
}
|
|
120
120
|
return vnode
|
|
@@ -122,85 +122,85 @@ const resolveComponent = (vnode: VNodeChild): VNode | null => {
|
|
|
122
122
|
|
|
123
123
|
// ─── Transition Mode (default) ────────────────────────────
|
|
124
124
|
|
|
125
|
-
describe(
|
|
126
|
-
it(
|
|
127
|
-
const FadeDiv = kinetic(
|
|
125
|
+
describe('kinetic() — transition mode', () => {
|
|
126
|
+
it('returns a VNode when show=true', () => {
|
|
127
|
+
const FadeDiv = kinetic('div').preset(fade)
|
|
128
128
|
const show = signal(true)
|
|
129
|
-
const vnode = FadeDiv({ show, children:
|
|
129
|
+
const vnode = FadeDiv({ show, children: 'Hello' })
|
|
130
130
|
expect(vnode).not.toBeNull()
|
|
131
131
|
})
|
|
132
132
|
|
|
133
|
-
it(
|
|
133
|
+
it('fires onEnter callback when entering', () => {
|
|
134
134
|
const onEnter = vi.fn()
|
|
135
135
|
const show = signal(false)
|
|
136
|
-
const Slide = kinetic(
|
|
137
|
-
.enter({ opacity: 0, transform:
|
|
138
|
-
.enterTo({ opacity: 1, transform:
|
|
139
|
-
.enterTransition(
|
|
136
|
+
const Slide = kinetic('div')
|
|
137
|
+
.enter({ opacity: 0, transform: 'translateY(16px)' })
|
|
138
|
+
.enterTo({ opacity: 1, transform: 'translateY(0)' })
|
|
139
|
+
.enterTransition('all 300ms ease')
|
|
140
140
|
|
|
141
|
-
const el = document.createElement(
|
|
142
|
-
const vnode = resolveComponent(Slide({ show, onEnter, children:
|
|
141
|
+
const el = document.createElement('div')
|
|
142
|
+
const vnode = resolveComponent(Slide({ show, onEnter, children: 'Hello' }))
|
|
143
143
|
wireRef(vnode, el)
|
|
144
144
|
|
|
145
145
|
show.set(true)
|
|
146
146
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
147
147
|
})
|
|
148
148
|
|
|
149
|
-
it(
|
|
149
|
+
it('applies enterStyle on entering', () => {
|
|
150
150
|
const show = signal(false)
|
|
151
|
-
const Slide = kinetic(
|
|
152
|
-
.enter({ opacity: 0, transform:
|
|
153
|
-
.enterTo({ opacity: 1, transform:
|
|
154
|
-
.enterTransition(
|
|
151
|
+
const Slide = kinetic('div')
|
|
152
|
+
.enter({ opacity: 0, transform: 'translateY(16px)' })
|
|
153
|
+
.enterTo({ opacity: 1, transform: 'translateY(0)' })
|
|
154
|
+
.enterTransition('all 300ms ease')
|
|
155
155
|
|
|
156
|
-
const el = document.createElement(
|
|
157
|
-
const vnode = resolveComponent(Slide({ show, children:
|
|
156
|
+
const el = document.createElement('div')
|
|
157
|
+
const vnode = resolveComponent(Slide({ show, children: 'Hello' }))
|
|
158
158
|
wireRef(vnode, el)
|
|
159
159
|
|
|
160
160
|
show.set(true)
|
|
161
161
|
|
|
162
|
-
expect(el.style.opacity).toBe(
|
|
163
|
-
expect(el.style.transition).toBe(
|
|
162
|
+
expect(el.style.opacity).toBe('0')
|
|
163
|
+
expect(el.style.transition).toBe('all 300ms ease')
|
|
164
164
|
|
|
165
165
|
flushRaf()
|
|
166
166
|
flushRaf()
|
|
167
167
|
|
|
168
|
-
expect(el.style.opacity).toBe(
|
|
168
|
+
expect(el.style.opacity).toBe('1')
|
|
169
169
|
})
|
|
170
170
|
|
|
171
|
-
it(
|
|
171
|
+
it('applies class-based transitions via .enterClass()', () => {
|
|
172
172
|
const show = signal(false)
|
|
173
|
-
const ClassFade = kinetic(
|
|
174
|
-
.enterClass({ active:
|
|
175
|
-
.leaveClass({ active:
|
|
173
|
+
const ClassFade = kinetic('div')
|
|
174
|
+
.enterClass({ active: 't-enter', from: 't-from', to: 't-to' })
|
|
175
|
+
.leaveClass({ active: 't-leave', from: 't-lfrom', to: 't-lto' })
|
|
176
176
|
|
|
177
|
-
const el = document.createElement(
|
|
178
|
-
const vnode = resolveComponent(ClassFade({ show, children:
|
|
177
|
+
const el = document.createElement('div')
|
|
178
|
+
const vnode = resolveComponent(ClassFade({ show, children: 'Hello' }))
|
|
179
179
|
wireRef(vnode, el)
|
|
180
180
|
|
|
181
181
|
show.set(true)
|
|
182
182
|
|
|
183
|
-
expect(el.classList.contains(
|
|
184
|
-
expect(el.classList.contains(
|
|
183
|
+
expect(el.classList.contains('t-enter')).toBe(true)
|
|
184
|
+
expect(el.classList.contains('t-from')).toBe(true)
|
|
185
185
|
|
|
186
186
|
flushRaf()
|
|
187
187
|
flushRaf()
|
|
188
188
|
|
|
189
|
-
expect(el.classList.contains(
|
|
190
|
-
expect(el.classList.contains(
|
|
189
|
+
expect(el.classList.contains('t-from')).toBe(false)
|
|
190
|
+
expect(el.classList.contains('t-to')).toBe(true)
|
|
191
191
|
})
|
|
192
192
|
|
|
193
|
-
it(
|
|
193
|
+
it('fires lifecycle callbacks at correct times', () => {
|
|
194
194
|
const onEnter = vi.fn()
|
|
195
195
|
const onAfterEnter = vi.fn()
|
|
196
196
|
const onLeave = vi.fn()
|
|
197
197
|
const onAfterLeave = vi.fn()
|
|
198
198
|
const show = signal(false)
|
|
199
199
|
|
|
200
|
-
const FadeDiv = kinetic(
|
|
201
|
-
const el = document.createElement(
|
|
200
|
+
const FadeDiv = kinetic('div').preset(fade)
|
|
201
|
+
const el = document.createElement('div')
|
|
202
202
|
const vnode = resolveComponent(
|
|
203
|
-
FadeDiv({ show, onEnter, onAfterEnter, onLeave, onAfterLeave, children:
|
|
203
|
+
FadeDiv({ show, onEnter, onAfterEnter, onLeave, onAfterLeave, children: 'Hello' }),
|
|
204
204
|
)
|
|
205
205
|
wireRef(vnode, el)
|
|
206
206
|
|
|
@@ -226,13 +226,13 @@ describe("kinetic() — transition mode", () => {
|
|
|
226
226
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
227
227
|
})
|
|
228
228
|
|
|
229
|
-
it(
|
|
229
|
+
it('timeout fallback completes transition', () => {
|
|
230
230
|
const onAfterEnter = vi.fn()
|
|
231
231
|
const show = signal(false)
|
|
232
|
-
const FadeDiv = kinetic(
|
|
232
|
+
const FadeDiv = kinetic('div').preset(fade).config({ timeout: 1000 })
|
|
233
233
|
|
|
234
|
-
const el = document.createElement(
|
|
235
|
-
const vnode = resolveComponent(FadeDiv({ show, onAfterEnter, children:
|
|
234
|
+
const el = document.createElement('div')
|
|
235
|
+
const vnode = resolveComponent(FadeDiv({ show, onAfterEnter, children: 'Hello' }))
|
|
236
236
|
wireRef(vnode, el)
|
|
237
237
|
|
|
238
238
|
show.set(true)
|
|
@@ -245,13 +245,13 @@ describe("kinetic() — transition mode", () => {
|
|
|
245
245
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
246
246
|
})
|
|
247
247
|
|
|
248
|
-
it(
|
|
248
|
+
it('appear=true animates on initial mount', () => {
|
|
249
249
|
const onEnter = vi.fn()
|
|
250
250
|
const show = signal(true)
|
|
251
|
-
const FadeDiv = kinetic(
|
|
251
|
+
const FadeDiv = kinetic('div').preset(fade).config({ appear: true }).on({ onEnter })
|
|
252
252
|
|
|
253
|
-
const el = document.createElement(
|
|
254
|
-
const vnode = resolveComponent(FadeDiv({ show, children:
|
|
253
|
+
const el = document.createElement('div')
|
|
254
|
+
const vnode = resolveComponent(FadeDiv({ show, children: 'Hello' }))
|
|
255
255
|
wireRef(vnode, el)
|
|
256
256
|
|
|
257
257
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
@@ -260,9 +260,9 @@ describe("kinetic() — transition mode", () => {
|
|
|
260
260
|
|
|
261
261
|
// ─── Chain Immutability ────────────────────────────────────
|
|
262
262
|
|
|
263
|
-
describe(
|
|
264
|
-
it(
|
|
265
|
-
const Base = kinetic(
|
|
263
|
+
describe('kinetic() — chaining', () => {
|
|
264
|
+
it('chain is immutable (each method returns new component)', () => {
|
|
265
|
+
const Base = kinetic('div')
|
|
266
266
|
const WithFade = Base.preset(fade)
|
|
267
267
|
const WithSlide = Base.preset(slideUp)
|
|
268
268
|
|
|
@@ -271,41 +271,41 @@ describe("kinetic() — chaining", () => {
|
|
|
271
271
|
expect(WithFade).not.toBe(Base)
|
|
272
272
|
})
|
|
273
273
|
|
|
274
|
-
it(
|
|
274
|
+
it('.preset() merges preset properties into config', () => {
|
|
275
275
|
const show = signal(false)
|
|
276
|
-
const FadeDiv = kinetic(
|
|
276
|
+
const FadeDiv = kinetic('div').preset(fade)
|
|
277
277
|
|
|
278
|
-
const el = document.createElement(
|
|
279
|
-
const vnode = resolveComponent(FadeDiv({ show, children:
|
|
278
|
+
const el = document.createElement('div')
|
|
279
|
+
const vnode = resolveComponent(FadeDiv({ show, children: 'Hello' }))
|
|
280
280
|
wireRef(vnode, el)
|
|
281
281
|
|
|
282
282
|
show.set(true)
|
|
283
283
|
|
|
284
|
-
expect(el.style.opacity).toBe(
|
|
285
|
-
expect(el.style.transition).toBe(
|
|
284
|
+
expect(el.style.opacity).toBe('0')
|
|
285
|
+
expect(el.style.transition).toBe('opacity 300ms ease-out')
|
|
286
286
|
})
|
|
287
287
|
|
|
288
|
-
it(
|
|
288
|
+
it('.on() callbacks from chain are used when runtime callbacks not provided', () => {
|
|
289
289
|
const onEnter = vi.fn()
|
|
290
290
|
const show = signal(false)
|
|
291
|
-
const FadeDiv = kinetic(
|
|
291
|
+
const FadeDiv = kinetic('div').preset(fade).on({ onEnter })
|
|
292
292
|
|
|
293
|
-
const el = document.createElement(
|
|
294
|
-
const vnode = resolveComponent(FadeDiv({ show, children:
|
|
293
|
+
const el = document.createElement('div')
|
|
294
|
+
const vnode = resolveComponent(FadeDiv({ show, children: 'Hello' }))
|
|
295
295
|
wireRef(vnode, el)
|
|
296
296
|
|
|
297
297
|
show.set(true)
|
|
298
298
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
299
299
|
})
|
|
300
300
|
|
|
301
|
-
it(
|
|
301
|
+
it('runtime props override chain config', () => {
|
|
302
302
|
const chainOnEnter = vi.fn()
|
|
303
303
|
const runtimeOnEnter = vi.fn()
|
|
304
304
|
const show = signal(false)
|
|
305
|
-
const FadeDiv = kinetic(
|
|
305
|
+
const FadeDiv = kinetic('div').preset(fade).on({ onEnter: chainOnEnter })
|
|
306
306
|
|
|
307
|
-
const el = document.createElement(
|
|
308
|
-
const vnode = resolveComponent(FadeDiv({ show, onEnter: runtimeOnEnter, children:
|
|
307
|
+
const el = document.createElement('div')
|
|
308
|
+
const vnode = resolveComponent(FadeDiv({ show, onEnter: runtimeOnEnter, children: 'Hello' }))
|
|
309
309
|
wireRef(vnode, el)
|
|
310
310
|
|
|
311
311
|
show.set(true)
|
|
@@ -314,17 +314,17 @@ describe("kinetic() — chaining", () => {
|
|
|
314
314
|
expect(chainOnEnter).not.toHaveBeenCalled()
|
|
315
315
|
})
|
|
316
316
|
|
|
317
|
-
it(
|
|
318
|
-
const FadeDiv = kinetic(
|
|
319
|
-
expect(FadeDiv.displayName).toBe(
|
|
317
|
+
it('displayName is set correctly', () => {
|
|
318
|
+
const FadeDiv = kinetic('div').preset(fade)
|
|
319
|
+
expect(FadeDiv.displayName).toBe('kinetic(div)')
|
|
320
320
|
})
|
|
321
321
|
})
|
|
322
322
|
|
|
323
323
|
// ─── Collapse Mode ─────────────────────────────────────────
|
|
324
324
|
|
|
325
|
-
describe(
|
|
325
|
+
describe('kinetic() — collapse mode', () => {
|
|
326
326
|
const mockScrollHeight = (value: number) => {
|
|
327
|
-
Object.defineProperty(HTMLElement.prototype,
|
|
327
|
+
Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
|
|
328
328
|
configurable: true,
|
|
329
329
|
get() {
|
|
330
330
|
return value
|
|
@@ -336,15 +336,15 @@ describe("kinetic() — collapse mode", () => {
|
|
|
336
336
|
mockScrollHeight(200)
|
|
337
337
|
})
|
|
338
338
|
|
|
339
|
-
it(
|
|
339
|
+
it('fires onEnter on entering and onAfterEnter after transitionend', () => {
|
|
340
340
|
const onEnter = vi.fn()
|
|
341
341
|
const onAfterEnter = vi.fn()
|
|
342
342
|
const show = signal(false)
|
|
343
|
-
const Accordion = kinetic(
|
|
343
|
+
const Accordion = kinetic('div').collapse()
|
|
344
344
|
|
|
345
|
-
const wrapperEl = document.createElement(
|
|
346
|
-
const contentEl = document.createElement(
|
|
347
|
-
Object.defineProperty(wrapperEl,
|
|
345
|
+
const wrapperEl = document.createElement('div')
|
|
346
|
+
const contentEl = document.createElement('div')
|
|
347
|
+
Object.defineProperty(wrapperEl, 'offsetHeight', {
|
|
348
348
|
configurable: true,
|
|
349
349
|
get: () => 0,
|
|
350
350
|
})
|
|
@@ -354,7 +354,7 @@ describe("kinetic() — collapse mode", () => {
|
|
|
354
354
|
show,
|
|
355
355
|
onEnter,
|
|
356
356
|
onAfterEnter,
|
|
357
|
-
children: { type:
|
|
357
|
+
children: { type: 'p', props: {}, children: ['Content'], key: null },
|
|
358
358
|
}),
|
|
359
359
|
)
|
|
360
360
|
|
|
@@ -369,10 +369,10 @@ describe("kinetic() — collapse mode", () => {
|
|
|
369
369
|
if (node.children) {
|
|
370
370
|
const children = Array.isArray(node.children) ? node.children : [node.children]
|
|
371
371
|
for (const child of children) {
|
|
372
|
-
if (child && typeof child ===
|
|
372
|
+
if (child && typeof child === 'object' && 'type' in (child as object)) {
|
|
373
373
|
const cNode = child as VNode
|
|
374
374
|
const props = cNode.props as Record<string, unknown>
|
|
375
|
-
if (props?.ref && typeof props.ref ===
|
|
375
|
+
if (props?.ref && typeof props.ref === 'object' && cNode.type === 'div') {
|
|
376
376
|
;(props.ref as { current: HTMLElement | null }).current = contentEl
|
|
377
377
|
}
|
|
378
378
|
findContentRef(cNode)
|
|
@@ -384,10 +384,10 @@ describe("kinetic() — collapse mode", () => {
|
|
|
384
384
|
if (props?.children) {
|
|
385
385
|
const pc = Array.isArray(props.children) ? props.children : [props.children]
|
|
386
386
|
for (const p of pc) {
|
|
387
|
-
if (p && typeof p ===
|
|
387
|
+
if (p && typeof p === 'object' && 'type' in (p as object)) {
|
|
388
388
|
const pNode = p as VNode
|
|
389
389
|
const pProps = pNode.props as Record<string, unknown>
|
|
390
|
-
if (pProps?.ref && typeof pProps.ref ===
|
|
390
|
+
if (pProps?.ref && typeof pProps.ref === 'object') {
|
|
391
391
|
;(pProps.ref as { current: HTMLElement | null }).current = contentEl
|
|
392
392
|
}
|
|
393
393
|
}
|
|
@@ -408,67 +408,67 @@ describe("kinetic() — collapse mode", () => {
|
|
|
408
408
|
|
|
409
409
|
// ─── Transition mode — leave with styles ─────────────────────
|
|
410
410
|
|
|
411
|
-
describe(
|
|
412
|
-
it(
|
|
411
|
+
describe('kinetic() — transition leave styles', () => {
|
|
412
|
+
it('applies leaveStyle and leaveTransition on leaving', () => {
|
|
413
413
|
const show = signal(true)
|
|
414
|
-
const Slide = kinetic(
|
|
414
|
+
const Slide = kinetic('div')
|
|
415
415
|
.enter({ opacity: 0 })
|
|
416
416
|
.enterTo({ opacity: 1 })
|
|
417
|
-
.enterTransition(
|
|
417
|
+
.enterTransition('opacity 300ms ease')
|
|
418
418
|
.leave({ opacity: 1 })
|
|
419
419
|
.leaveTo({ opacity: 0 })
|
|
420
|
-
.leaveTransition(
|
|
420
|
+
.leaveTransition('opacity 200ms ease-in')
|
|
421
421
|
|
|
422
|
-
const el = document.createElement(
|
|
423
|
-
const vnode = resolveComponent(Slide({ show, children:
|
|
422
|
+
const el = document.createElement('div')
|
|
423
|
+
const vnode = resolveComponent(Slide({ show, children: 'Hello' }))
|
|
424
424
|
wireRef(vnode, el)
|
|
425
425
|
|
|
426
426
|
show.set(false)
|
|
427
427
|
|
|
428
|
-
expect(el.style.opacity).toBe(
|
|
429
|
-
expect(el.style.transition).toBe(
|
|
428
|
+
expect(el.style.opacity).toBe('1')
|
|
429
|
+
expect(el.style.transition).toBe('opacity 200ms ease-in')
|
|
430
430
|
|
|
431
431
|
flushRaf()
|
|
432
432
|
flushRaf()
|
|
433
433
|
|
|
434
|
-
expect(el.style.opacity).toBe(
|
|
434
|
+
expect(el.style.opacity).toBe('0')
|
|
435
435
|
})
|
|
436
436
|
})
|
|
437
437
|
|
|
438
438
|
// ─── Config defaults ──────────────────────────────────────
|
|
439
439
|
|
|
440
|
-
describe(
|
|
441
|
-
it(
|
|
440
|
+
describe('kinetic() — config defaults and overrides', () => {
|
|
441
|
+
it('appear from config is used when runtime appear not provided', () => {
|
|
442
442
|
const onEnter = vi.fn()
|
|
443
443
|
const show = signal(true)
|
|
444
|
-
const FadeDiv = kinetic(
|
|
444
|
+
const FadeDiv = kinetic('div').preset(fade).config({ appear: true })
|
|
445
445
|
|
|
446
|
-
const el = document.createElement(
|
|
447
|
-
const vnode = resolveComponent(FadeDiv({ show, onEnter, children:
|
|
446
|
+
const el = document.createElement('div')
|
|
447
|
+
const vnode = resolveComponent(FadeDiv({ show, onEnter, children: 'Hello' }))
|
|
448
448
|
wireRef(vnode, el)
|
|
449
449
|
|
|
450
450
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
451
451
|
})
|
|
452
452
|
|
|
453
|
-
it(
|
|
453
|
+
it('runtime appear overrides config appear', () => {
|
|
454
454
|
const onEnter = vi.fn()
|
|
455
455
|
const show = signal(true)
|
|
456
|
-
const FadeDiv = kinetic(
|
|
456
|
+
const FadeDiv = kinetic('div').preset(fade).config({ appear: true })
|
|
457
457
|
|
|
458
|
-
const el = document.createElement(
|
|
459
|
-
const vnode = resolveComponent(FadeDiv({ show, appear: false, onEnter, children:
|
|
458
|
+
const el = document.createElement('div')
|
|
459
|
+
const vnode = resolveComponent(FadeDiv({ show, appear: false, onEnter, children: 'Hello' }))
|
|
460
460
|
wireRef(vnode, el)
|
|
461
461
|
|
|
462
462
|
expect(onEnter).not.toHaveBeenCalled()
|
|
463
463
|
})
|
|
464
464
|
|
|
465
|
-
it(
|
|
465
|
+
it('timeout from config is used as fallback', () => {
|
|
466
466
|
const onAfterEnter = vi.fn()
|
|
467
467
|
const show = signal(false)
|
|
468
|
-
const FadeDiv = kinetic(
|
|
468
|
+
const FadeDiv = kinetic('div').preset(fade).config({ timeout: 200 })
|
|
469
469
|
|
|
470
|
-
const el = document.createElement(
|
|
471
|
-
const vnode = resolveComponent(FadeDiv({ show, onAfterEnter, children:
|
|
470
|
+
const el = document.createElement('div')
|
|
471
|
+
const vnode = resolveComponent(FadeDiv({ show, onAfterEnter, children: 'Hello' }))
|
|
472
472
|
wireRef(vnode, el)
|
|
473
473
|
|
|
474
474
|
show.set(true)
|
|
@@ -482,13 +482,13 @@ describe("kinetic() — config defaults and overrides", () => {
|
|
|
482
482
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
483
483
|
})
|
|
484
484
|
|
|
485
|
-
it(
|
|
485
|
+
it('runtime timeout overrides config timeout', () => {
|
|
486
486
|
const onAfterEnter = vi.fn()
|
|
487
487
|
const show = signal(false)
|
|
488
|
-
const FadeDiv = kinetic(
|
|
488
|
+
const FadeDiv = kinetic('div').preset(fade).config({ timeout: 200 })
|
|
489
489
|
|
|
490
|
-
const el = document.createElement(
|
|
491
|
-
const vnode = resolveComponent(FadeDiv({ show, timeout: 500, onAfterEnter, children:
|
|
490
|
+
const el = document.createElement('div')
|
|
491
|
+
const vnode = resolveComponent(FadeDiv({ show, timeout: 500, onAfterEnter, children: 'Hello' }))
|
|
492
492
|
wireRef(vnode, el)
|
|
493
493
|
|
|
494
494
|
show.set(true)
|
|
@@ -507,16 +507,16 @@ describe("kinetic() — config defaults and overrides", () => {
|
|
|
507
507
|
|
|
508
508
|
// ─── DisplayName ───────────────────────────────────────────
|
|
509
509
|
|
|
510
|
-
describe(
|
|
511
|
-
it(
|
|
512
|
-
const FadeDiv = kinetic(
|
|
513
|
-
expect(FadeDiv.displayName).toBe(
|
|
510
|
+
describe('kinetic() — displayName', () => {
|
|
511
|
+
it('uses tag string for displayName', () => {
|
|
512
|
+
const FadeDiv = kinetic('div').preset(fade)
|
|
513
|
+
expect(FadeDiv.displayName).toBe('kinetic(div)')
|
|
514
514
|
})
|
|
515
515
|
})
|
|
516
516
|
|
|
517
517
|
// ─── Reduced Motion ───────────────────────────────────────
|
|
518
518
|
|
|
519
|
-
describe(
|
|
519
|
+
describe('kinetic() — transition reduced motion', () => {
|
|
520
520
|
beforeEach(() => {
|
|
521
521
|
_reducedMotion = true
|
|
522
522
|
})
|
|
@@ -525,14 +525,14 @@ describe("kinetic() — transition reduced motion", () => {
|
|
|
525
525
|
_reducedMotion = false
|
|
526
526
|
})
|
|
527
527
|
|
|
528
|
-
it(
|
|
528
|
+
it('reduced motion: entering fires onEnter and onAfterEnter immediately without rAF', () => {
|
|
529
529
|
const show = signal(false)
|
|
530
530
|
const onEnter = vi.fn()
|
|
531
531
|
const onAfterEnter = vi.fn()
|
|
532
|
-
const FadeDiv = kinetic(
|
|
532
|
+
const FadeDiv = kinetic('div').preset(fade)
|
|
533
533
|
|
|
534
|
-
const el = document.createElement(
|
|
535
|
-
const vnode = resolveComponent(FadeDiv({ show, onEnter, onAfterEnter, children:
|
|
534
|
+
const el = document.createElement('div')
|
|
535
|
+
const vnode = resolveComponent(FadeDiv({ show, onEnter, onAfterEnter, children: 'Hello' }))
|
|
536
536
|
wireRef(vnode, el)
|
|
537
537
|
|
|
538
538
|
show.set(true)
|
|
@@ -543,14 +543,14 @@ describe("kinetic() — transition reduced motion", () => {
|
|
|
543
543
|
expect(rafCallbacks.length).toBe(0)
|
|
544
544
|
})
|
|
545
545
|
|
|
546
|
-
it(
|
|
546
|
+
it('reduced motion: leaving fires onLeave and onAfterLeave immediately without rAF', () => {
|
|
547
547
|
const show = signal(true)
|
|
548
548
|
const onLeave = vi.fn()
|
|
549
549
|
const onAfterLeave = vi.fn()
|
|
550
|
-
const FadeDiv = kinetic(
|
|
550
|
+
const FadeDiv = kinetic('div').preset(fade)
|
|
551
551
|
|
|
552
|
-
const el = document.createElement(
|
|
553
|
-
const vnode = resolveComponent(FadeDiv({ show, onLeave, onAfterLeave, children:
|
|
552
|
+
const el = document.createElement('div')
|
|
553
|
+
const vnode = resolveComponent(FadeDiv({ show, onLeave, onAfterLeave, children: 'Hello' }))
|
|
554
554
|
wireRef(vnode, el)
|
|
555
555
|
|
|
556
556
|
show.set(false)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fade, presets, scaleIn, slideDown, slideLeft, slideRight, slideUp } from
|
|
1
|
+
import { fade, presets, scaleIn, slideDown, slideLeft, slideRight, slideUp } from '../presets'
|
|
2
2
|
|
|
3
|
-
describe(
|
|
3
|
+
describe('presets', () => {
|
|
4
4
|
const allPresets = {
|
|
5
5
|
fade,
|
|
6
6
|
scaleIn,
|
|
@@ -10,7 +10,7 @@ describe("presets", () => {
|
|
|
10
10
|
slideRight,
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
it.each(Object.entries(allPresets))(
|
|
13
|
+
it.each(Object.entries(allPresets))('%s has all required style properties', (_, preset) => {
|
|
14
14
|
expect(preset.enterStyle).toBeDefined()
|
|
15
15
|
expect(preset.enterToStyle).toBeDefined()
|
|
16
16
|
expect(preset.enterTransition).toBeDefined()
|
|
@@ -19,27 +19,27 @@ describe("presets", () => {
|
|
|
19
19
|
expect(preset.leaveTransition).toBeDefined()
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
it.each(Object.entries(allPresets))(
|
|
23
|
-
expect(typeof preset.enterTransition).toBe(
|
|
22
|
+
it.each(Object.entries(allPresets))('%s has non-empty transition strings', (_, preset) => {
|
|
23
|
+
expect(typeof preset.enterTransition).toBe('string')
|
|
24
24
|
expect((preset.enterTransition as string).length).toBeGreaterThan(0)
|
|
25
|
-
expect(typeof preset.leaveTransition).toBe(
|
|
25
|
+
expect(typeof preset.leaveTransition).toBe('string')
|
|
26
26
|
expect((preset.leaveTransition as string).length).toBeGreaterThan(0)
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
it(
|
|
29
|
+
it('presets object contains all expected presets', () => {
|
|
30
30
|
expect(Object.keys(presets)).toEqual([
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
'fade',
|
|
32
|
+
'scaleIn',
|
|
33
|
+
'slideUp',
|
|
34
|
+
'slideDown',
|
|
35
|
+
'slideLeft',
|
|
36
|
+
'slideRight',
|
|
37
37
|
])
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
it(
|
|
40
|
+
it('presets are plain objects (no side effects)', () => {
|
|
41
41
|
for (const preset of Object.values(presets)) {
|
|
42
|
-
expect(typeof preset).toBe(
|
|
42
|
+
expect(typeof preset).toBe('object')
|
|
43
43
|
expect(preset).not.toBeInstanceOf(Array)
|
|
44
44
|
}
|
|
45
45
|
})
|