@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 Transition from
|
|
10
|
+
import Transition from '../Transition'
|
|
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
|
|
|
@@ -56,27 +56,27 @@ const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
|
56
56
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: complex logic is inherent to this function
|
|
57
57
|
const visitNode = (node: VNode) => {
|
|
58
58
|
const nodeProps = node.props as Record<string, unknown>
|
|
59
|
-
if (typeof nodeProps?.ref ===
|
|
59
|
+
if (typeof nodeProps?.ref === 'function') {
|
|
60
60
|
;(nodeProps.ref as (element: HTMLElement | null) => void)(el)
|
|
61
|
-
} else if (nodeProps?.ref && typeof nodeProps.ref ===
|
|
61
|
+
} else if (nodeProps?.ref && typeof nodeProps.ref === 'object') {
|
|
62
62
|
;(nodeProps.ref as { current: HTMLElement | null }).current = el
|
|
63
63
|
}
|
|
64
64
|
if (node.children) {
|
|
65
65
|
const ch = Array.isArray(node.children) ? node.children : [node.children]
|
|
66
66
|
for (const c of ch) {
|
|
67
|
-
if (c && typeof c ===
|
|
67
|
+
if (c && typeof c === 'object' && 'type' in (c as object)) visitNode(c as VNode)
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
if (nodeProps?.children) {
|
|
71
71
|
const pc = Array.isArray(nodeProps.children) ? nodeProps.children : [nodeProps.children]
|
|
72
72
|
for (const c of pc) {
|
|
73
|
-
if (c && typeof c ===
|
|
73
|
+
if (c && typeof c === 'object' && 'type' in (c as object)) visitNode(c as VNode)
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
if (
|
|
77
77
|
nodeProps?.fallback &&
|
|
78
|
-
typeof nodeProps.fallback ===
|
|
79
|
-
|
|
78
|
+
typeof nodeProps.fallback === 'object' &&
|
|
79
|
+
'type' in (nodeProps.fallback as object)
|
|
80
80
|
) {
|
|
81
81
|
visitNode(nodeProps.fallback as VNode)
|
|
82
82
|
}
|
|
@@ -88,11 +88,11 @@ const wireRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
|
88
88
|
* Helper: call Transition and wire up a mock element to the merged ref.
|
|
89
89
|
*/
|
|
90
90
|
const setupTransition = (props: Record<string, unknown>) => {
|
|
91
|
-
const el = document.createElement(
|
|
91
|
+
const el = document.createElement('div')
|
|
92
92
|
const child: VNode = {
|
|
93
|
-
type:
|
|
94
|
-
props: {
|
|
95
|
-
children: [
|
|
93
|
+
type: 'div',
|
|
94
|
+
props: { 'data-testid': 'child' },
|
|
95
|
+
children: ['Hello'],
|
|
96
96
|
key: null,
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -106,24 +106,24 @@ const setupTransition = (props: Record<string, unknown>) => {
|
|
|
106
106
|
return { vnode, el }
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
describe(
|
|
110
|
-
it(
|
|
109
|
+
describe('Transition', () => {
|
|
110
|
+
it('returns a VNode when show=true', () => {
|
|
111
111
|
const show = signal(true)
|
|
112
|
-
const child: VNode = { type:
|
|
112
|
+
const child: VNode = { type: 'div', props: {}, children: ['Hello'], key: null }
|
|
113
113
|
const vnode = Transition({ show, children: child })
|
|
114
114
|
expect(vnode).not.toBeNull()
|
|
115
115
|
})
|
|
116
116
|
|
|
117
|
-
it(
|
|
117
|
+
it('returns a VNode with Show component', () => {
|
|
118
118
|
const show = signal(true)
|
|
119
|
-
const child: VNode = { type:
|
|
119
|
+
const child: VNode = { type: 'div', props: {}, children: ['Hello'], key: null }
|
|
120
120
|
const vnode = Transition({ show, children: child })
|
|
121
121
|
expect(vnode).not.toBeNull()
|
|
122
122
|
// The outermost VNode should be a Show component
|
|
123
|
-
expect(typeof vnode?.type).toBe(
|
|
123
|
+
expect(typeof vnode?.type).toBe('function')
|
|
124
124
|
})
|
|
125
125
|
|
|
126
|
-
it(
|
|
126
|
+
it('fires onEnter callback when entering starts', () => {
|
|
127
127
|
const show = signal(false)
|
|
128
128
|
const onEnter = vi.fn()
|
|
129
129
|
|
|
@@ -135,7 +135,7 @@ describe("Transition", () => {
|
|
|
135
135
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
136
136
|
})
|
|
137
137
|
|
|
138
|
-
it(
|
|
138
|
+
it('fires onLeave callback when leaving starts', () => {
|
|
139
139
|
const show = signal(true)
|
|
140
140
|
const onLeave = vi.fn()
|
|
141
141
|
|
|
@@ -145,7 +145,7 @@ describe("Transition", () => {
|
|
|
145
145
|
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
-
it(
|
|
148
|
+
it('fires onAfterEnter after transitionend', () => {
|
|
149
149
|
const show = signal(false)
|
|
150
150
|
const onAfterEnter = vi.fn()
|
|
151
151
|
|
|
@@ -161,7 +161,7 @@ describe("Transition", () => {
|
|
|
161
161
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
162
162
|
})
|
|
163
163
|
|
|
164
|
-
it(
|
|
164
|
+
it('fires onAfterLeave after transitionend', () => {
|
|
165
165
|
const show = signal(true)
|
|
166
166
|
const onAfterLeave = vi.fn()
|
|
167
167
|
|
|
@@ -177,29 +177,29 @@ describe("Transition", () => {
|
|
|
177
177
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
178
178
|
})
|
|
179
179
|
|
|
180
|
-
it(
|
|
180
|
+
it('applies enter classes on entering', () => {
|
|
181
181
|
const show = signal(false)
|
|
182
182
|
const { el } = setupTransition({
|
|
183
183
|
show,
|
|
184
|
-
enter:
|
|
185
|
-
enterFrom:
|
|
186
|
-
enterTo:
|
|
184
|
+
enter: 't-enter',
|
|
185
|
+
enterFrom: 't-enter-from',
|
|
186
|
+
enterTo: 't-enter-to',
|
|
187
187
|
})
|
|
188
188
|
|
|
189
189
|
show.set(true)
|
|
190
190
|
|
|
191
|
-
expect(el.classList.contains(
|
|
192
|
-
expect(el.classList.contains(
|
|
193
|
-
expect(el.classList.contains(
|
|
191
|
+
expect(el.classList.contains('t-enter')).toBe(true)
|
|
192
|
+
expect(el.classList.contains('t-enter-from')).toBe(true)
|
|
193
|
+
expect(el.classList.contains('t-enter-to')).toBe(false)
|
|
194
194
|
})
|
|
195
195
|
|
|
196
|
-
it(
|
|
196
|
+
it('swaps enterFrom to enterTo after double rAF', () => {
|
|
197
197
|
const show = signal(false)
|
|
198
198
|
const { el } = setupTransition({
|
|
199
199
|
show,
|
|
200
|
-
enter:
|
|
201
|
-
enterFrom:
|
|
202
|
-
enterTo:
|
|
200
|
+
enter: 't-enter',
|
|
201
|
+
enterFrom: 't-enter-from',
|
|
202
|
+
enterTo: 't-enter-to',
|
|
203
203
|
})
|
|
204
204
|
|
|
205
205
|
show.set(true)
|
|
@@ -207,18 +207,18 @@ describe("Transition", () => {
|
|
|
207
207
|
flushRaf()
|
|
208
208
|
flushRaf()
|
|
209
209
|
|
|
210
|
-
expect(el.classList.contains(
|
|
211
|
-
expect(el.classList.contains(
|
|
212
|
-
expect(el.classList.contains(
|
|
210
|
+
expect(el.classList.contains('t-enter')).toBe(true)
|
|
211
|
+
expect(el.classList.contains('t-enter-from')).toBe(false)
|
|
212
|
+
expect(el.classList.contains('t-enter-to')).toBe(true)
|
|
213
213
|
})
|
|
214
214
|
|
|
215
|
-
it(
|
|
215
|
+
it('cleans up enter classes after transitionend', () => {
|
|
216
216
|
const show = signal(false)
|
|
217
217
|
const { el } = setupTransition({
|
|
218
218
|
show,
|
|
219
|
-
enter:
|
|
220
|
-
enterFrom:
|
|
221
|
-
enterTo:
|
|
219
|
+
enter: 't-enter',
|
|
220
|
+
enterFrom: 't-enter-from',
|
|
221
|
+
enterTo: 't-enter-to',
|
|
222
222
|
})
|
|
223
223
|
|
|
224
224
|
show.set(true)
|
|
@@ -227,71 +227,71 @@ describe("Transition", () => {
|
|
|
227
227
|
fireTransitionEnd(el)
|
|
228
228
|
|
|
229
229
|
// enter class should be removed on entered stage
|
|
230
|
-
expect(el.classList.contains(
|
|
230
|
+
expect(el.classList.contains('t-enter')).toBe(false)
|
|
231
231
|
})
|
|
232
232
|
|
|
233
|
-
it(
|
|
233
|
+
it('applies style-object transitions on entering', () => {
|
|
234
234
|
const show = signal(false)
|
|
235
235
|
const { el } = setupTransition({
|
|
236
236
|
show,
|
|
237
237
|
enterStyle: { opacity: 0 },
|
|
238
238
|
enterToStyle: { opacity: 1 },
|
|
239
|
-
enterTransition:
|
|
239
|
+
enterTransition: 'opacity 300ms ease',
|
|
240
240
|
})
|
|
241
241
|
|
|
242
242
|
show.set(true)
|
|
243
243
|
|
|
244
|
-
expect(el.style.opacity).toBe(
|
|
245
|
-
expect(el.style.transition).toBe(
|
|
244
|
+
expect(el.style.opacity).toBe('0')
|
|
245
|
+
expect(el.style.transition).toBe('opacity 300ms ease')
|
|
246
246
|
|
|
247
247
|
flushRaf()
|
|
248
248
|
flushRaf()
|
|
249
249
|
|
|
250
|
-
expect(el.style.opacity).toBe(
|
|
250
|
+
expect(el.style.opacity).toBe('1')
|
|
251
251
|
})
|
|
252
252
|
|
|
253
|
-
it(
|
|
253
|
+
it('applies leave classes on leaving', () => {
|
|
254
254
|
const show = signal(true)
|
|
255
255
|
const { el } = setupTransition({
|
|
256
256
|
show,
|
|
257
|
-
leave:
|
|
258
|
-
leaveFrom:
|
|
259
|
-
leaveTo:
|
|
257
|
+
leave: 't-leave',
|
|
258
|
+
leaveFrom: 't-leave-from',
|
|
259
|
+
leaveTo: 't-leave-to',
|
|
260
260
|
})
|
|
261
261
|
|
|
262
262
|
show.set(false)
|
|
263
263
|
|
|
264
|
-
expect(el.classList.contains(
|
|
265
|
-
expect(el.classList.contains(
|
|
264
|
+
expect(el.classList.contains('t-leave')).toBe(true)
|
|
265
|
+
expect(el.classList.contains('t-leave-from')).toBe(true)
|
|
266
266
|
|
|
267
267
|
flushRaf()
|
|
268
268
|
flushRaf()
|
|
269
269
|
|
|
270
|
-
expect(el.classList.contains(
|
|
271
|
-
expect(el.classList.contains(
|
|
270
|
+
expect(el.classList.contains('t-leave-from')).toBe(false)
|
|
271
|
+
expect(el.classList.contains('t-leave-to')).toBe(true)
|
|
272
272
|
})
|
|
273
273
|
|
|
274
|
-
it(
|
|
274
|
+
it('applies leave style transitions', () => {
|
|
275
275
|
const show = signal(true)
|
|
276
276
|
const { el } = setupTransition({
|
|
277
277
|
show,
|
|
278
278
|
leaveStyle: { opacity: 1 },
|
|
279
279
|
leaveToStyle: { opacity: 0 },
|
|
280
|
-
leaveTransition:
|
|
280
|
+
leaveTransition: 'opacity 200ms ease-in',
|
|
281
281
|
})
|
|
282
282
|
|
|
283
283
|
show.set(false)
|
|
284
284
|
|
|
285
|
-
expect(el.style.opacity).toBe(
|
|
286
|
-
expect(el.style.transition).toBe(
|
|
285
|
+
expect(el.style.opacity).toBe('1')
|
|
286
|
+
expect(el.style.transition).toBe('opacity 200ms ease-in')
|
|
287
287
|
|
|
288
288
|
flushRaf()
|
|
289
289
|
flushRaf()
|
|
290
290
|
|
|
291
|
-
expect(el.style.opacity).toBe(
|
|
291
|
+
expect(el.style.opacity).toBe('0')
|
|
292
292
|
})
|
|
293
293
|
|
|
294
|
-
it(
|
|
294
|
+
it('appear=true fires onEnter on initial mount', () => {
|
|
295
295
|
const show = signal(true)
|
|
296
296
|
const onEnter = vi.fn()
|
|
297
297
|
|
|
@@ -300,7 +300,7 @@ describe("Transition", () => {
|
|
|
300
300
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
301
301
|
})
|
|
302
302
|
|
|
303
|
-
it(
|
|
303
|
+
it('timeout fallback completes transition when transitionend never fires', () => {
|
|
304
304
|
const show = signal(false)
|
|
305
305
|
const onAfterEnter = vi.fn()
|
|
306
306
|
|
|
@@ -315,54 +315,54 @@ describe("Transition", () => {
|
|
|
315
315
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
316
316
|
})
|
|
317
317
|
|
|
318
|
-
it(
|
|
318
|
+
it('cleans up transition style on entered stage', () => {
|
|
319
319
|
const show = signal(false)
|
|
320
320
|
const { el } = setupTransition({
|
|
321
321
|
show,
|
|
322
|
-
enter:
|
|
323
|
-
enterTransition:
|
|
322
|
+
enter: 't-enter',
|
|
323
|
+
enterTransition: 'opacity 300ms ease',
|
|
324
324
|
enterStyle: { opacity: 0 },
|
|
325
325
|
enterToStyle: { opacity: 1 },
|
|
326
326
|
})
|
|
327
327
|
|
|
328
328
|
show.set(true)
|
|
329
|
-
expect(el.style.transition).toBe(
|
|
330
|
-
expect(el.classList.contains(
|
|
329
|
+
expect(el.style.transition).toBe('opacity 300ms ease')
|
|
330
|
+
expect(el.classList.contains('t-enter')).toBe(true)
|
|
331
331
|
|
|
332
332
|
flushRaf()
|
|
333
333
|
flushRaf()
|
|
334
334
|
fireTransitionEnd(el)
|
|
335
335
|
|
|
336
336
|
// After entering -> entered, transition reset and enter class removed
|
|
337
|
-
expect(el.style.transition).toBe(
|
|
338
|
-
expect(el.classList.contains(
|
|
337
|
+
expect(el.style.transition).toBe('')
|
|
338
|
+
expect(el.classList.contains('t-enter')).toBe(false)
|
|
339
339
|
})
|
|
340
340
|
})
|
|
341
341
|
|
|
342
|
-
describe(
|
|
342
|
+
describe('Transition — reduced motion', () => {
|
|
343
343
|
beforeEach(() => {
|
|
344
344
|
vi.useFakeTimers()
|
|
345
345
|
rafCallbacks = []
|
|
346
346
|
_reducedMotion = true
|
|
347
347
|
|
|
348
348
|
vi.stubGlobal(
|
|
349
|
-
|
|
349
|
+
'requestAnimationFrame',
|
|
350
350
|
vi.fn((cb: () => void) => {
|
|
351
351
|
rafCallbacks.push(cb)
|
|
352
352
|
return rafCallbacks.length
|
|
353
353
|
}),
|
|
354
354
|
)
|
|
355
|
-
vi.stubGlobal(
|
|
355
|
+
vi.stubGlobal('cancelAnimationFrame', vi.fn())
|
|
356
356
|
})
|
|
357
357
|
|
|
358
358
|
afterEach(() => {
|
|
359
359
|
vi.useRealTimers()
|
|
360
|
-
vi.stubGlobal(
|
|
361
|
-
vi.stubGlobal(
|
|
360
|
+
vi.stubGlobal('requestAnimationFrame', originalRaf)
|
|
361
|
+
vi.stubGlobal('cancelAnimationFrame', originalCaf)
|
|
362
362
|
_reducedMotion = false
|
|
363
363
|
})
|
|
364
364
|
|
|
365
|
-
it(
|
|
365
|
+
it('reduced motion: entering fires onEnter and onAfterEnter immediately', () => {
|
|
366
366
|
const show = signal(false)
|
|
367
367
|
const onEnter = vi.fn()
|
|
368
368
|
const onAfterEnter = vi.fn()
|
|
@@ -375,7 +375,7 @@ describe("Transition — reduced motion", () => {
|
|
|
375
375
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
376
376
|
})
|
|
377
377
|
|
|
378
|
-
it(
|
|
378
|
+
it('reduced motion: leaving fires onLeave and onAfterLeave immediately', () => {
|
|
379
379
|
const show = signal(true)
|
|
380
380
|
const onLeave = vi.fn()
|
|
381
381
|
const onAfterLeave = vi.fn()
|
|
@@ -388,19 +388,19 @@ describe("Transition — reduced motion", () => {
|
|
|
388
388
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
389
389
|
})
|
|
390
390
|
|
|
391
|
-
it(
|
|
391
|
+
it('reduced motion: does not use rAF or apply CSS classes', () => {
|
|
392
392
|
const show = signal(false)
|
|
393
393
|
const { el } = setupTransition({
|
|
394
394
|
show,
|
|
395
|
-
enter:
|
|
396
|
-
enterFrom:
|
|
397
|
-
enterTo:
|
|
395
|
+
enter: 't-enter',
|
|
396
|
+
enterFrom: 't-enter-from',
|
|
397
|
+
enterTo: 't-enter-to',
|
|
398
398
|
})
|
|
399
399
|
|
|
400
400
|
show.set(true)
|
|
401
401
|
|
|
402
|
-
expect(el.classList.contains(
|
|
403
|
-
expect(el.classList.contains(
|
|
402
|
+
expect(el.classList.contains('t-enter')).toBe(false)
|
|
403
|
+
expect(el.classList.contains('t-enter-from')).toBe(false)
|
|
404
404
|
expect(rafCallbacks.length).toBe(0)
|
|
405
405
|
})
|
|
406
406
|
})
|