@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,13 +1,13 @@
|
|
|
1
|
-
import type { VNode } from
|
|
2
|
-
import { signal } from
|
|
1
|
+
import type { VNode } 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 TransitionItem from
|
|
10
|
+
import TransitionItem from '../kinetic/TransitionItem'
|
|
11
11
|
|
|
12
12
|
// Mock rAF for deterministic double-rAF testing
|
|
13
13
|
let rafCallbacks: (() => void)[] = []
|
|
@@ -19,20 +19,20 @@ beforeEach(() => {
|
|
|
19
19
|
rafCallbacks = []
|
|
20
20
|
|
|
21
21
|
vi.stubGlobal(
|
|
22
|
-
|
|
22
|
+
'requestAnimationFrame',
|
|
23
23
|
vi.fn((cb: () => void) => {
|
|
24
24
|
rafCallbacks.push(cb)
|
|
25
25
|
return rafCallbacks.length
|
|
26
26
|
}),
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
-
vi.stubGlobal(
|
|
29
|
+
vi.stubGlobal('cancelAnimationFrame', vi.fn())
|
|
30
30
|
})
|
|
31
31
|
|
|
32
32
|
afterEach(() => {
|
|
33
33
|
vi.useRealTimers()
|
|
34
|
-
vi.stubGlobal(
|
|
35
|
-
vi.stubGlobal(
|
|
34
|
+
vi.stubGlobal('requestAnimationFrame', originalRaf)
|
|
35
|
+
vi.stubGlobal('cancelAnimationFrame', originalCaf)
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
const flushRaf = () => {
|
|
@@ -42,8 +42,8 @@ const flushRaf = () => {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const fireTransitionEnd = (el: HTMLElement) => {
|
|
45
|
-
const event = new Event(
|
|
46
|
-
Object.defineProperty(event,
|
|
45
|
+
const event = new Event('transitionend', { bubbles: true })
|
|
46
|
+
Object.defineProperty(event, 'target', { value: el })
|
|
47
47
|
el.dispatchEvent(event)
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -55,27 +55,27 @@ const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
|
55
55
|
if (!vnode) return
|
|
56
56
|
const visitNode = (node: VNode) => {
|
|
57
57
|
const nodeProps = node.props as Record<string, unknown>
|
|
58
|
-
if (typeof nodeProps?.ref ===
|
|
58
|
+
if (typeof nodeProps?.ref === 'function') {
|
|
59
59
|
;(nodeProps.ref as (element: HTMLElement | null) => void)(el)
|
|
60
|
-
} else if (nodeProps?.ref && typeof nodeProps.ref ===
|
|
60
|
+
} else if (nodeProps?.ref && typeof nodeProps.ref === 'object') {
|
|
61
61
|
;(nodeProps.ref as { current: HTMLElement | null }).current = el
|
|
62
62
|
}
|
|
63
63
|
if (node.children) {
|
|
64
64
|
const ch = Array.isArray(node.children) ? node.children : [node.children]
|
|
65
65
|
for (const c of ch) {
|
|
66
|
-
if (c && typeof c ===
|
|
66
|
+
if (c && typeof c === 'object' && 'type' in (c as object)) visitNode(c as VNode)
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
if (nodeProps?.children) {
|
|
70
70
|
const pc = Array.isArray(nodeProps.children) ? nodeProps.children : [nodeProps.children]
|
|
71
71
|
for (const c of pc) {
|
|
72
|
-
if (c && typeof c ===
|
|
72
|
+
if (c && typeof c === 'object' && 'type' in (c as object)) visitNode(c as VNode)
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
if (
|
|
76
76
|
nodeProps?.fallback &&
|
|
77
|
-
typeof nodeProps.fallback ===
|
|
78
|
-
|
|
77
|
+
typeof nodeProps.fallback === 'object' &&
|
|
78
|
+
'type' in (nodeProps.fallback as object)
|
|
79
79
|
) {
|
|
80
80
|
visitNode(nodeProps.fallback as VNode)
|
|
81
81
|
}
|
|
@@ -87,11 +87,11 @@ const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
|
87
87
|
* Helper: call TransitionItem and wire a mock element to refs.
|
|
88
88
|
*/
|
|
89
89
|
const setupTransitionItem = (props: Record<string, unknown>) => {
|
|
90
|
-
const el = document.createElement(
|
|
90
|
+
const el = document.createElement('div')
|
|
91
91
|
const child: VNode = {
|
|
92
|
-
type:
|
|
93
|
-
props: {
|
|
94
|
-
children: [
|
|
92
|
+
type: 'div',
|
|
93
|
+
props: { 'data-testid': 'child' },
|
|
94
|
+
children: ['Hello'],
|
|
95
95
|
key: null,
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -105,25 +105,25 @@ const setupTransitionItem = (props: Record<string, unknown>) => {
|
|
|
105
105
|
return { vnode, el }
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
describe(
|
|
109
|
-
it(
|
|
108
|
+
describe('TransitionItem', () => {
|
|
109
|
+
it('returns a VNode when show returns true', () => {
|
|
110
110
|
const show = () => true
|
|
111
|
-
const child: VNode = { type:
|
|
111
|
+
const child: VNode = { type: 'div', props: {}, children: ['Hello'], key: null }
|
|
112
112
|
const vnode = TransitionItem({ show, children: child })
|
|
113
113
|
expect(vnode).not.toBeNull()
|
|
114
114
|
})
|
|
115
115
|
|
|
116
|
-
it(
|
|
116
|
+
it('wraps child in a Show component', () => {
|
|
117
117
|
const show = () => true
|
|
118
|
-
const child: VNode = { type:
|
|
118
|
+
const child: VNode = { type: 'div', props: {}, children: ['Hello'], key: null }
|
|
119
119
|
const vnode = TransitionItem({ show, children: child })
|
|
120
120
|
expect(vnode).not.toBeNull()
|
|
121
|
-
expect(typeof vnode?.type).toBe(
|
|
121
|
+
expect(typeof vnode?.type).toBe('function')
|
|
122
122
|
})
|
|
123
123
|
|
|
124
|
-
it(
|
|
124
|
+
it('clones child VNode with merged ref', () => {
|
|
125
125
|
const show = () => true
|
|
126
|
-
const child: VNode = { type:
|
|
126
|
+
const child: VNode = { type: 'div', props: {}, children: ['Hello'], key: null }
|
|
127
127
|
const vnode = TransitionItem({ show, children: child })
|
|
128
128
|
|
|
129
129
|
// The Show component's children (or fallback) should have a ref prop
|
|
@@ -132,11 +132,11 @@ describe("TransitionItem", () => {
|
|
|
132
132
|
if (showChildren) {
|
|
133
133
|
const childProps = showChildren.props as Record<string, unknown>
|
|
134
134
|
expect(childProps?.ref).toBeDefined()
|
|
135
|
-
expect(typeof childProps?.ref).toBe(
|
|
135
|
+
expect(typeof childProps?.ref).toBe('function')
|
|
136
136
|
}
|
|
137
137
|
})
|
|
138
138
|
|
|
139
|
-
it(
|
|
139
|
+
it('fires onEnter callback when entering', () => {
|
|
140
140
|
const show = signal(false)
|
|
141
141
|
const onEnter = vi.fn()
|
|
142
142
|
|
|
@@ -146,7 +146,7 @@ describe("TransitionItem", () => {
|
|
|
146
146
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
147
147
|
})
|
|
148
148
|
|
|
149
|
-
it(
|
|
149
|
+
it('fires onLeave callback when leaving', () => {
|
|
150
150
|
const show = signal(true)
|
|
151
151
|
const onLeave = vi.fn()
|
|
152
152
|
|
|
@@ -156,29 +156,29 @@ describe("TransitionItem", () => {
|
|
|
156
156
|
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
157
157
|
})
|
|
158
158
|
|
|
159
|
-
it(
|
|
159
|
+
it('applies enter classes when entering', () => {
|
|
160
160
|
const show = signal(false)
|
|
161
161
|
const { el } = setupTransitionItem({
|
|
162
162
|
show: () => show(),
|
|
163
|
-
enter:
|
|
164
|
-
enterFrom:
|
|
165
|
-
enterTo:
|
|
163
|
+
enter: 'ti-enter',
|
|
164
|
+
enterFrom: 'ti-enter-from',
|
|
165
|
+
enterTo: 'ti-enter-to',
|
|
166
166
|
})
|
|
167
167
|
|
|
168
168
|
show.set(true)
|
|
169
169
|
|
|
170
|
-
expect(el.classList.contains(
|
|
171
|
-
expect(el.classList.contains(
|
|
172
|
-
expect(el.classList.contains(
|
|
170
|
+
expect(el.classList.contains('ti-enter')).toBe(true)
|
|
171
|
+
expect(el.classList.contains('ti-enter-from')).toBe(true)
|
|
172
|
+
expect(el.classList.contains('ti-enter-to')).toBe(false)
|
|
173
173
|
})
|
|
174
174
|
|
|
175
|
-
it(
|
|
175
|
+
it('swaps enterFrom to enterTo after double rAF', () => {
|
|
176
176
|
const show = signal(false)
|
|
177
177
|
const { el } = setupTransitionItem({
|
|
178
178
|
show: () => show(),
|
|
179
|
-
enter:
|
|
180
|
-
enterFrom:
|
|
181
|
-
enterTo:
|
|
179
|
+
enter: 'ti-enter',
|
|
180
|
+
enterFrom: 'ti-enter-from',
|
|
181
|
+
enterTo: 'ti-enter-to',
|
|
182
182
|
})
|
|
183
183
|
|
|
184
184
|
show.set(true)
|
|
@@ -186,73 +186,73 @@ describe("TransitionItem", () => {
|
|
|
186
186
|
flushRaf()
|
|
187
187
|
flushRaf()
|
|
188
188
|
|
|
189
|
-
expect(el.classList.contains(
|
|
190
|
-
expect(el.classList.contains(
|
|
191
|
-
expect(el.classList.contains(
|
|
189
|
+
expect(el.classList.contains('ti-enter')).toBe(true)
|
|
190
|
+
expect(el.classList.contains('ti-enter-from')).toBe(false)
|
|
191
|
+
expect(el.classList.contains('ti-enter-to')).toBe(true)
|
|
192
192
|
})
|
|
193
193
|
|
|
194
|
-
it(
|
|
194
|
+
it('applies leave classes when leaving', () => {
|
|
195
195
|
const show = signal(true)
|
|
196
196
|
const { el } = setupTransitionItem({
|
|
197
197
|
show: () => show(),
|
|
198
|
-
leave:
|
|
199
|
-
leaveFrom:
|
|
200
|
-
leaveTo:
|
|
198
|
+
leave: 'ti-leave',
|
|
199
|
+
leaveFrom: 'ti-leave-from',
|
|
200
|
+
leaveTo: 'ti-leave-to',
|
|
201
201
|
})
|
|
202
202
|
|
|
203
203
|
show.set(false)
|
|
204
204
|
|
|
205
|
-
expect(el.classList.contains(
|
|
206
|
-
expect(el.classList.contains(
|
|
205
|
+
expect(el.classList.contains('ti-leave')).toBe(true)
|
|
206
|
+
expect(el.classList.contains('ti-leave-from')).toBe(true)
|
|
207
207
|
|
|
208
208
|
flushRaf()
|
|
209
209
|
flushRaf()
|
|
210
210
|
|
|
211
|
-
expect(el.classList.contains(
|
|
212
|
-
expect(el.classList.contains(
|
|
211
|
+
expect(el.classList.contains('ti-leave-from')).toBe(false)
|
|
212
|
+
expect(el.classList.contains('ti-leave-to')).toBe(true)
|
|
213
213
|
})
|
|
214
214
|
|
|
215
|
-
it(
|
|
215
|
+
it('applies enter style transitions', () => {
|
|
216
216
|
const show = signal(false)
|
|
217
217
|
const { el } = setupTransitionItem({
|
|
218
218
|
show: () => show(),
|
|
219
219
|
enterStyle: { opacity: 0 },
|
|
220
220
|
enterToStyle: { opacity: 1 },
|
|
221
|
-
enterTransition:
|
|
221
|
+
enterTransition: 'opacity 300ms ease',
|
|
222
222
|
})
|
|
223
223
|
|
|
224
224
|
show.set(true)
|
|
225
225
|
|
|
226
|
-
expect(el.style.opacity).toBe(
|
|
227
|
-
expect(el.style.transition).toBe(
|
|
226
|
+
expect(el.style.opacity).toBe('0')
|
|
227
|
+
expect(el.style.transition).toBe('opacity 300ms ease')
|
|
228
228
|
|
|
229
229
|
flushRaf()
|
|
230
230
|
flushRaf()
|
|
231
231
|
|
|
232
|
-
expect(el.style.opacity).toBe(
|
|
232
|
+
expect(el.style.opacity).toBe('1')
|
|
233
233
|
})
|
|
234
234
|
|
|
235
|
-
it(
|
|
235
|
+
it('applies leave style transitions', () => {
|
|
236
236
|
const show = signal(true)
|
|
237
237
|
const { el } = setupTransitionItem({
|
|
238
238
|
show: () => show(),
|
|
239
239
|
leaveStyle: { opacity: 1 },
|
|
240
240
|
leaveToStyle: { opacity: 0 },
|
|
241
|
-
leaveTransition:
|
|
241
|
+
leaveTransition: 'opacity 200ms ease-in',
|
|
242
242
|
})
|
|
243
243
|
|
|
244
244
|
show.set(false)
|
|
245
245
|
|
|
246
|
-
expect(el.style.opacity).toBe(
|
|
247
|
-
expect(el.style.transition).toBe(
|
|
246
|
+
expect(el.style.opacity).toBe('1')
|
|
247
|
+
expect(el.style.transition).toBe('opacity 200ms ease-in')
|
|
248
248
|
|
|
249
249
|
flushRaf()
|
|
250
250
|
flushRaf()
|
|
251
251
|
|
|
252
|
-
expect(el.style.opacity).toBe(
|
|
252
|
+
expect(el.style.opacity).toBe('0')
|
|
253
253
|
})
|
|
254
254
|
|
|
255
|
-
it(
|
|
255
|
+
it('fires onAfterEnter after transitionend', () => {
|
|
256
256
|
const show = signal(false)
|
|
257
257
|
const onAfterEnter = vi.fn()
|
|
258
258
|
|
|
@@ -268,7 +268,7 @@ describe("TransitionItem", () => {
|
|
|
268
268
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
269
269
|
})
|
|
270
270
|
|
|
271
|
-
it(
|
|
271
|
+
it('fires onAfterLeave after transitionend', () => {
|
|
272
272
|
const show = signal(true)
|
|
273
273
|
const onAfterLeave = vi.fn()
|
|
274
274
|
|
|
@@ -284,13 +284,13 @@ describe("TransitionItem", () => {
|
|
|
284
284
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
285
285
|
})
|
|
286
286
|
|
|
287
|
-
it(
|
|
287
|
+
it('cleans up enter classes after transitionend', () => {
|
|
288
288
|
const show = signal(false)
|
|
289
289
|
const { el } = setupTransitionItem({
|
|
290
290
|
show: () => show(),
|
|
291
|
-
enter:
|
|
292
|
-
enterFrom:
|
|
293
|
-
enterTo:
|
|
291
|
+
enter: 'ti-enter',
|
|
292
|
+
enterFrom: 'ti-enter-from',
|
|
293
|
+
enterTo: 'ti-enter-to',
|
|
294
294
|
})
|
|
295
295
|
|
|
296
296
|
show.set(true)
|
|
@@ -299,32 +299,32 @@ describe("TransitionItem", () => {
|
|
|
299
299
|
fireTransitionEnd(el)
|
|
300
300
|
|
|
301
301
|
// enter class should be removed on entered stage
|
|
302
|
-
expect(el.classList.contains(
|
|
302
|
+
expect(el.classList.contains('ti-enter')).toBe(false)
|
|
303
303
|
})
|
|
304
304
|
|
|
305
|
-
it(
|
|
305
|
+
it('cleans up transition style on entered stage', () => {
|
|
306
306
|
const show = signal(false)
|
|
307
307
|
const { el } = setupTransitionItem({
|
|
308
308
|
show: () => show(),
|
|
309
|
-
enter:
|
|
310
|
-
enterTransition:
|
|
309
|
+
enter: 'ti-enter',
|
|
310
|
+
enterTransition: 'opacity 300ms ease',
|
|
311
311
|
enterStyle: { opacity: 0 },
|
|
312
312
|
enterToStyle: { opacity: 1 },
|
|
313
313
|
})
|
|
314
314
|
|
|
315
315
|
show.set(true)
|
|
316
|
-
expect(el.style.transition).toBe(
|
|
317
|
-
expect(el.classList.contains(
|
|
316
|
+
expect(el.style.transition).toBe('opacity 300ms ease')
|
|
317
|
+
expect(el.classList.contains('ti-enter')).toBe(true)
|
|
318
318
|
|
|
319
319
|
flushRaf()
|
|
320
320
|
flushRaf()
|
|
321
321
|
fireTransitionEnd(el)
|
|
322
322
|
|
|
323
|
-
expect(el.style.transition).toBe(
|
|
324
|
-
expect(el.classList.contains(
|
|
323
|
+
expect(el.style.transition).toBe('')
|
|
324
|
+
expect(el.classList.contains('ti-enter')).toBe(false)
|
|
325
325
|
})
|
|
326
326
|
|
|
327
|
-
it(
|
|
327
|
+
it('appear=true fires onEnter on initial mount', () => {
|
|
328
328
|
const show = signal(true)
|
|
329
329
|
const onEnter = vi.fn()
|
|
330
330
|
|
|
@@ -333,7 +333,7 @@ describe("TransitionItem", () => {
|
|
|
333
333
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
334
334
|
})
|
|
335
335
|
|
|
336
|
-
it(
|
|
336
|
+
it('appear=false does not fire onEnter on initial mount when show is true', () => {
|
|
337
337
|
const show = signal(true)
|
|
338
338
|
const onEnter = vi.fn()
|
|
339
339
|
|
|
@@ -342,7 +342,7 @@ describe("TransitionItem", () => {
|
|
|
342
342
|
expect(onEnter).not.toHaveBeenCalled()
|
|
343
343
|
})
|
|
344
344
|
|
|
345
|
-
it(
|
|
345
|
+
it('timeout fallback completes transition when transitionend never fires', () => {
|
|
346
346
|
const show = signal(false)
|
|
347
347
|
const onAfterEnter = vi.fn()
|
|
348
348
|
|
|
@@ -356,79 +356,79 @@ describe("TransitionItem", () => {
|
|
|
356
356
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
357
357
|
})
|
|
358
358
|
|
|
359
|
-
it(
|
|
359
|
+
it('unmount=false keeps element with display:none when hidden', () => {
|
|
360
360
|
const show = () => false
|
|
361
|
-
const child: VNode = { type:
|
|
361
|
+
const child: VNode = { type: 'div', props: {}, children: ['Hello'], key: null }
|
|
362
362
|
const vnode = TransitionItem({ show, unmount: false, children: child })
|
|
363
363
|
|
|
364
364
|
expect(vnode).not.toBeNull()
|
|
365
365
|
// With unmount=false, the fallback should contain a cloned VNode with display:none
|
|
366
366
|
const showProps = vnode?.props as Record<string, unknown>
|
|
367
|
-
if (showProps?.fallback && typeof showProps.fallback ===
|
|
367
|
+
if (showProps?.fallback && typeof showProps.fallback === 'object') {
|
|
368
368
|
const fallbackNode = showProps.fallback as VNode
|
|
369
369
|
const fallbackProps = fallbackNode.props as Record<string, unknown>
|
|
370
370
|
const style = fallbackProps?.style as Record<string, unknown> | undefined
|
|
371
|
-
expect(style?.display).toBe(
|
|
371
|
+
expect(style?.display).toBe('none')
|
|
372
372
|
}
|
|
373
373
|
})
|
|
374
374
|
|
|
375
|
-
it(
|
|
375
|
+
it('unmount=false fallback has a merged ref', () => {
|
|
376
376
|
const show = () => false
|
|
377
|
-
const child: VNode = { type:
|
|
377
|
+
const child: VNode = { type: 'div', props: {}, children: ['Hello'], key: null }
|
|
378
378
|
const vnode = TransitionItem({ show, unmount: false, children: child })
|
|
379
379
|
|
|
380
380
|
const showProps = vnode?.props as Record<string, unknown>
|
|
381
|
-
if (showProps?.fallback && typeof showProps.fallback ===
|
|
381
|
+
if (showProps?.fallback && typeof showProps.fallback === 'object') {
|
|
382
382
|
const fallbackNode = showProps.fallback as VNode
|
|
383
383
|
const fallbackProps = fallbackNode.props as Record<string, unknown>
|
|
384
384
|
expect(fallbackProps?.ref).toBeDefined()
|
|
385
|
-
expect(typeof fallbackProps?.ref).toBe(
|
|
385
|
+
expect(typeof fallbackProps?.ref).toBe('function')
|
|
386
386
|
}
|
|
387
387
|
})
|
|
388
388
|
|
|
389
|
-
it(
|
|
389
|
+
it('unmount=false merges existing child style with display:none', () => {
|
|
390
390
|
const show = () => false
|
|
391
391
|
const child: VNode = {
|
|
392
|
-
type:
|
|
393
|
-
props: { style: { color:
|
|
394
|
-
children: [
|
|
392
|
+
type: 'div',
|
|
393
|
+
props: { style: { color: 'red', opacity: 1 } },
|
|
394
|
+
children: ['Hello'],
|
|
395
395
|
key: null,
|
|
396
396
|
}
|
|
397
397
|
const vnode = TransitionItem({ show, unmount: false, children: child })
|
|
398
398
|
|
|
399
399
|
const showProps = vnode?.props as Record<string, unknown>
|
|
400
|
-
if (showProps?.fallback && typeof showProps.fallback ===
|
|
400
|
+
if (showProps?.fallback && typeof showProps.fallback === 'object') {
|
|
401
401
|
const fallbackNode = showProps.fallback as VNode
|
|
402
402
|
const fallbackProps = fallbackNode.props as Record<string, unknown>
|
|
403
403
|
const style = fallbackProps?.style as Record<string, unknown> | undefined
|
|
404
|
-
expect(style?.color).toBe(
|
|
404
|
+
expect(style?.color).toBe('red')
|
|
405
405
|
expect(style?.opacity).toBe(1)
|
|
406
|
-
expect(style?.display).toBe(
|
|
406
|
+
expect(style?.display).toBe('none')
|
|
407
407
|
}
|
|
408
408
|
})
|
|
409
409
|
|
|
410
|
-
it(
|
|
410
|
+
it('appear=true applies enter classes on initial mount when show is true', () => {
|
|
411
411
|
const show = signal(true)
|
|
412
412
|
const { el } = setupTransitionItem({
|
|
413
413
|
show: () => show(),
|
|
414
414
|
appear: true,
|
|
415
|
-
enter:
|
|
416
|
-
enterFrom:
|
|
417
|
-
enterTo:
|
|
415
|
+
enter: 'ti-enter',
|
|
416
|
+
enterFrom: 'ti-enter-from',
|
|
417
|
+
enterTo: 'ti-enter-to',
|
|
418
418
|
})
|
|
419
419
|
|
|
420
420
|
// After appear, entering classes should be applied
|
|
421
|
-
expect(el.classList.contains(
|
|
422
|
-
expect(el.classList.contains(
|
|
421
|
+
expect(el.classList.contains('ti-enter')).toBe(true)
|
|
422
|
+
expect(el.classList.contains('ti-enter-from')).toBe(true)
|
|
423
423
|
|
|
424
424
|
flushRaf()
|
|
425
425
|
flushRaf()
|
|
426
426
|
|
|
427
|
-
expect(el.classList.contains(
|
|
428
|
-
expect(el.classList.contains(
|
|
427
|
+
expect(el.classList.contains('ti-enter-from')).toBe(false)
|
|
428
|
+
expect(el.classList.contains('ti-enter-to')).toBe(true)
|
|
429
429
|
})
|
|
430
430
|
|
|
431
|
-
it(
|
|
431
|
+
it('appear=true completes full enter lifecycle', () => {
|
|
432
432
|
const show = signal(true)
|
|
433
433
|
const onEnter = vi.fn()
|
|
434
434
|
const onAfterEnter = vi.fn()
|
|
@@ -438,7 +438,7 @@ describe("TransitionItem", () => {
|
|
|
438
438
|
appear: true,
|
|
439
439
|
onEnter,
|
|
440
440
|
onAfterEnter,
|
|
441
|
-
enter:
|
|
441
|
+
enter: 'ti-enter',
|
|
442
442
|
})
|
|
443
443
|
|
|
444
444
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
@@ -449,35 +449,35 @@ describe("TransitionItem", () => {
|
|
|
449
449
|
|
|
450
450
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
451
451
|
// After entered stage, enter class should be cleaned up
|
|
452
|
-
expect(el.classList.contains(
|
|
453
|
-
expect(el.style.transition).toBe(
|
|
452
|
+
expect(el.classList.contains('ti-enter')).toBe(false)
|
|
453
|
+
expect(el.style.transition).toBe('')
|
|
454
454
|
})
|
|
455
455
|
})
|
|
456
456
|
|
|
457
|
-
describe(
|
|
457
|
+
describe('TransitionItem — reduced motion', () => {
|
|
458
458
|
beforeEach(() => {
|
|
459
459
|
vi.useFakeTimers()
|
|
460
460
|
rafCallbacks = []
|
|
461
461
|
_reducedMotion = true
|
|
462
462
|
|
|
463
463
|
vi.stubGlobal(
|
|
464
|
-
|
|
464
|
+
'requestAnimationFrame',
|
|
465
465
|
vi.fn((cb: () => void) => {
|
|
466
466
|
rafCallbacks.push(cb)
|
|
467
467
|
return rafCallbacks.length
|
|
468
468
|
}),
|
|
469
469
|
)
|
|
470
|
-
vi.stubGlobal(
|
|
470
|
+
vi.stubGlobal('cancelAnimationFrame', vi.fn())
|
|
471
471
|
})
|
|
472
472
|
|
|
473
473
|
afterEach(() => {
|
|
474
474
|
vi.useRealTimers()
|
|
475
|
-
vi.stubGlobal(
|
|
476
|
-
vi.stubGlobal(
|
|
475
|
+
vi.stubGlobal('requestAnimationFrame', originalRaf)
|
|
476
|
+
vi.stubGlobal('cancelAnimationFrame', originalCaf)
|
|
477
477
|
_reducedMotion = false
|
|
478
478
|
})
|
|
479
479
|
|
|
480
|
-
it(
|
|
480
|
+
it('reduced motion: entering fires onEnter and onAfterEnter immediately', () => {
|
|
481
481
|
const show = signal(false)
|
|
482
482
|
const onEnter = vi.fn()
|
|
483
483
|
const onAfterEnter = vi.fn()
|
|
@@ -490,7 +490,7 @@ describe("TransitionItem — reduced motion", () => {
|
|
|
490
490
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
491
491
|
})
|
|
492
492
|
|
|
493
|
-
it(
|
|
493
|
+
it('reduced motion: leaving fires onLeave and onAfterLeave immediately', () => {
|
|
494
494
|
const show = signal(true)
|
|
495
495
|
const onLeave = vi.fn()
|
|
496
496
|
const onAfterLeave = vi.fn()
|
|
@@ -503,20 +503,20 @@ describe("TransitionItem — reduced motion", () => {
|
|
|
503
503
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
504
504
|
})
|
|
505
505
|
|
|
506
|
-
it(
|
|
506
|
+
it('reduced motion: does not apply CSS classes or rAF', () => {
|
|
507
507
|
const show = signal(false)
|
|
508
508
|
const { el } = setupTransitionItem({
|
|
509
509
|
show: () => show(),
|
|
510
|
-
enter:
|
|
511
|
-
enterFrom:
|
|
512
|
-
enterTo:
|
|
510
|
+
enter: 'ti-enter',
|
|
511
|
+
enterFrom: 'ti-enter-from',
|
|
512
|
+
enterTo: 'ti-enter-to',
|
|
513
513
|
})
|
|
514
514
|
|
|
515
515
|
show.set(true)
|
|
516
516
|
|
|
517
517
|
// No classes should be applied — reduced motion skips CSS transitions
|
|
518
|
-
expect(el.classList.contains(
|
|
519
|
-
expect(el.classList.contains(
|
|
518
|
+
expect(el.classList.contains('ti-enter')).toBe(false)
|
|
519
|
+
expect(el.classList.contains('ti-enter-from')).toBe(false)
|
|
520
520
|
expect(rafCallbacks.length).toBe(0)
|
|
521
521
|
})
|
|
522
522
|
})
|