@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,18 +1,18 @@
|
|
|
1
|
-
import type { VNode } from
|
|
2
|
-
import { signal } from
|
|
3
|
-
import Collapse from
|
|
4
|
-
import CollapseRenderer from
|
|
5
|
-
import type { KineticConfig } from
|
|
1
|
+
import type { VNode } from '@pyreon/core'
|
|
2
|
+
import { signal } from '@pyreon/reactivity'
|
|
3
|
+
import Collapse from '../Collapse'
|
|
4
|
+
import CollapseRenderer from '../kinetic/CollapseRenderer'
|
|
5
|
+
import type { KineticConfig } from '../kinetic/types'
|
|
6
6
|
|
|
7
7
|
let _reducedMotion = false
|
|
8
8
|
|
|
9
|
-
vi.mock(
|
|
9
|
+
vi.mock('../useReducedMotion', () => ({
|
|
10
10
|
useReducedMotion: () => () => _reducedMotion,
|
|
11
11
|
}))
|
|
12
12
|
|
|
13
13
|
// Mock scrollHeight
|
|
14
14
|
const mockScrollHeight = (value: number) => {
|
|
15
|
-
Object.defineProperty(HTMLElement.prototype,
|
|
15
|
+
Object.defineProperty(HTMLElement.prototype, 'scrollHeight', {
|
|
16
16
|
configurable: true,
|
|
17
17
|
get() {
|
|
18
18
|
return value
|
|
@@ -21,8 +21,8 @@ const mockScrollHeight = (value: number) => {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const fireTransitionEnd = (el: HTMLElement) => {
|
|
24
|
-
const event = new Event(
|
|
25
|
-
Object.defineProperty(event,
|
|
24
|
+
const event = new Event('transitionend', { bubbles: true })
|
|
25
|
+
Object.defineProperty(event, 'target', { value: el })
|
|
26
26
|
el.dispatchEvent(event)
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -33,11 +33,11 @@ const fireTransitionEnd = (el: HTMLElement) => {
|
|
|
33
33
|
*/
|
|
34
34
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: complex logic is inherent to this function
|
|
35
35
|
const setupCollapse = (props: Record<string, unknown>) => {
|
|
36
|
-
const wrapperEl = document.createElement(
|
|
37
|
-
const contentEl = document.createElement(
|
|
36
|
+
const wrapperEl = document.createElement('div')
|
|
37
|
+
const contentEl = document.createElement('div')
|
|
38
38
|
|
|
39
39
|
// Mock offsetHeight for reflow forcing
|
|
40
|
-
Object.defineProperty(wrapperEl,
|
|
40
|
+
Object.defineProperty(wrapperEl, 'offsetHeight', {
|
|
41
41
|
configurable: true,
|
|
42
42
|
get() {
|
|
43
43
|
return 0
|
|
@@ -51,9 +51,9 @@ const setupCollapse = (props: Record<string, unknown>) => {
|
|
|
51
51
|
if (vnode?.props) {
|
|
52
52
|
const vnodeProps = vnode.props as Record<string, unknown>
|
|
53
53
|
// wrapperRef is on the outer div
|
|
54
|
-
if (typeof vnodeProps.ref ===
|
|
54
|
+
if (typeof vnodeProps.ref === 'function') {
|
|
55
55
|
;(vnodeProps.ref as (el: HTMLElement | null) => void)(wrapperEl)
|
|
56
|
-
} else if (vnodeProps.ref && typeof vnodeProps.ref ===
|
|
56
|
+
} else if (vnodeProps.ref && typeof vnodeProps.ref === 'object') {
|
|
57
57
|
;(vnodeProps.ref as { current: HTMLElement | null }).current = wrapperEl
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -62,18 +62,18 @@ const setupCollapse = (props: Record<string, unknown>) => {
|
|
|
62
62
|
if (vnode?.children) {
|
|
63
63
|
const children = Array.isArray(vnode.children) ? vnode.children : [vnode.children]
|
|
64
64
|
for (const child of children) {
|
|
65
|
-
if (child && typeof child ===
|
|
65
|
+
if (child && typeof child === 'object' && 'type' in (child as object)) {
|
|
66
66
|
const showNode = child as any
|
|
67
67
|
// Show's children contain <div ref={contentRef}>
|
|
68
68
|
const showChildren = showNode.props?.children ?? showNode.children
|
|
69
69
|
if (showChildren) {
|
|
70
70
|
const sc = Array.isArray(showChildren) ? showChildren : [showChildren]
|
|
71
71
|
for (const s of sc) {
|
|
72
|
-
if (s && typeof s ===
|
|
72
|
+
if (s && typeof s === 'object' && 'props' in s) {
|
|
73
73
|
const ref = s.props?.ref
|
|
74
|
-
if (ref && typeof ref ===
|
|
74
|
+
if (ref && typeof ref === 'object') {
|
|
75
75
|
ref.current = contentEl
|
|
76
|
-
} else if (typeof ref ===
|
|
76
|
+
} else if (typeof ref === 'function') {
|
|
77
77
|
ref(contentEl)
|
|
78
78
|
}
|
|
79
79
|
}
|
|
@@ -86,7 +86,7 @@ const setupCollapse = (props: Record<string, unknown>) => {
|
|
|
86
86
|
return { vnode, wrapperEl, contentEl }
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
describe(
|
|
89
|
+
describe('Collapse', () => {
|
|
90
90
|
beforeEach(() => {
|
|
91
91
|
vi.useFakeTimers()
|
|
92
92
|
mockScrollHeight(200)
|
|
@@ -94,35 +94,35 @@ describe("Collapse", () => {
|
|
|
94
94
|
|
|
95
95
|
afterEach(() => vi.useRealTimers())
|
|
96
96
|
|
|
97
|
-
it(
|
|
97
|
+
it('returns a VNode', () => {
|
|
98
98
|
const show = signal(true)
|
|
99
|
-
const child = { type:
|
|
99
|
+
const child = { type: 'div', props: {}, children: ['Hello'], key: undefined }
|
|
100
100
|
const vnode = Collapse({ show, children: child } as any)
|
|
101
101
|
expect(vnode).not.toBeNull()
|
|
102
102
|
})
|
|
103
103
|
|
|
104
|
-
it(
|
|
104
|
+
it('fires onEnter callback when entering', () => {
|
|
105
105
|
const show = signal(false)
|
|
106
106
|
const onEnter = vi.fn()
|
|
107
107
|
|
|
108
108
|
setupCollapse({
|
|
109
109
|
show,
|
|
110
110
|
onEnter,
|
|
111
|
-
children: { type:
|
|
111
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
112
112
|
})
|
|
113
113
|
|
|
114
114
|
show.set(true)
|
|
115
115
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
116
116
|
})
|
|
117
117
|
|
|
118
|
-
it(
|
|
118
|
+
it('fires onAfterEnter after transitionend', () => {
|
|
119
119
|
const show = signal(false)
|
|
120
120
|
const onAfterEnter = vi.fn()
|
|
121
121
|
|
|
122
122
|
const { wrapperEl } = setupCollapse({
|
|
123
123
|
show,
|
|
124
124
|
onAfterEnter,
|
|
125
|
-
children: { type:
|
|
125
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
126
126
|
})
|
|
127
127
|
|
|
128
128
|
show.set(true)
|
|
@@ -132,28 +132,28 @@ describe("Collapse", () => {
|
|
|
132
132
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
133
133
|
})
|
|
134
134
|
|
|
135
|
-
it(
|
|
135
|
+
it('fires onLeave callback when leaving', () => {
|
|
136
136
|
const show = signal(true)
|
|
137
137
|
const onLeave = vi.fn()
|
|
138
138
|
|
|
139
139
|
setupCollapse({
|
|
140
140
|
show,
|
|
141
141
|
onLeave,
|
|
142
|
-
children: { type:
|
|
142
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
143
143
|
})
|
|
144
144
|
|
|
145
145
|
show.set(false)
|
|
146
146
|
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
147
147
|
})
|
|
148
148
|
|
|
149
|
-
it(
|
|
149
|
+
it('fires onAfterLeave after transitionend', () => {
|
|
150
150
|
const show = signal(true)
|
|
151
151
|
const onAfterLeave = vi.fn()
|
|
152
152
|
|
|
153
153
|
const { wrapperEl } = setupCollapse({
|
|
154
154
|
show,
|
|
155
155
|
onAfterLeave,
|
|
156
|
-
children: { type:
|
|
156
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
157
157
|
})
|
|
158
158
|
|
|
159
159
|
show.set(false)
|
|
@@ -163,65 +163,65 @@ describe("Collapse", () => {
|
|
|
163
163
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
164
164
|
})
|
|
165
165
|
|
|
166
|
-
it(
|
|
166
|
+
it('animates height from 0 to scrollHeight on enter', () => {
|
|
167
167
|
const show = signal(false)
|
|
168
168
|
|
|
169
169
|
const { wrapperEl } = setupCollapse({
|
|
170
170
|
show,
|
|
171
|
-
children: { type:
|
|
171
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
172
172
|
})
|
|
173
173
|
|
|
174
174
|
show.set(true)
|
|
175
175
|
|
|
176
|
-
expect(wrapperEl.style.height).toBe(
|
|
177
|
-
expect(wrapperEl.style.transition).toBe(
|
|
176
|
+
expect(wrapperEl.style.height).toBe('200px')
|
|
177
|
+
expect(wrapperEl.style.transition).toBe('height 300ms ease')
|
|
178
178
|
})
|
|
179
179
|
|
|
180
|
-
it(
|
|
180
|
+
it('switches to height:auto after enter animation completes', () => {
|
|
181
181
|
const show = signal(false)
|
|
182
182
|
|
|
183
183
|
const { wrapperEl } = setupCollapse({
|
|
184
184
|
show,
|
|
185
|
-
children: { type:
|
|
185
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
186
186
|
})
|
|
187
187
|
|
|
188
188
|
show.set(true)
|
|
189
189
|
fireTransitionEnd(wrapperEl)
|
|
190
190
|
|
|
191
|
-
expect(wrapperEl.style.height).toBe(
|
|
192
|
-
expect(wrapperEl.style.overflow).toBe(
|
|
193
|
-
expect(wrapperEl.style.transition).toBe(
|
|
191
|
+
expect(wrapperEl.style.height).toBe('auto')
|
|
192
|
+
expect(wrapperEl.style.overflow).toBe('')
|
|
193
|
+
expect(wrapperEl.style.transition).toBe('')
|
|
194
194
|
})
|
|
195
195
|
|
|
196
|
-
it(
|
|
196
|
+
it('animates height to 0 on leave', () => {
|
|
197
197
|
const show = signal(true)
|
|
198
198
|
|
|
199
199
|
const { wrapperEl } = setupCollapse({
|
|
200
200
|
show,
|
|
201
|
-
children: { type:
|
|
201
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
202
202
|
})
|
|
203
203
|
|
|
204
204
|
show.set(false)
|
|
205
205
|
|
|
206
|
-
expect(wrapperEl.style.height).toBe(
|
|
207
|
-
expect(wrapperEl.style.overflow).toBe(
|
|
206
|
+
expect(wrapperEl.style.height).toBe('0px')
|
|
207
|
+
expect(wrapperEl.style.overflow).toBe('hidden')
|
|
208
208
|
})
|
|
209
209
|
|
|
210
|
-
it(
|
|
210
|
+
it('uses custom transition property', () => {
|
|
211
211
|
const show = signal(false)
|
|
212
212
|
|
|
213
213
|
const { wrapperEl } = setupCollapse({
|
|
214
214
|
show,
|
|
215
|
-
transition:
|
|
216
|
-
children: { type:
|
|
215
|
+
transition: 'height 500ms ease-in-out',
|
|
216
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
217
217
|
})
|
|
218
218
|
|
|
219
219
|
show.set(true)
|
|
220
220
|
|
|
221
|
-
expect(wrapperEl.style.transition).toBe(
|
|
221
|
+
expect(wrapperEl.style.transition).toBe('height 500ms ease-in-out')
|
|
222
222
|
})
|
|
223
223
|
|
|
224
|
-
it(
|
|
224
|
+
it('appear=true animates on initial mount', async () => {
|
|
225
225
|
const show = signal(true)
|
|
226
226
|
const onEnter = vi.fn()
|
|
227
227
|
|
|
@@ -229,17 +229,17 @@ describe("Collapse", () => {
|
|
|
229
229
|
show,
|
|
230
230
|
appear: true,
|
|
231
231
|
onEnter,
|
|
232
|
-
children: { type:
|
|
232
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
233
233
|
})
|
|
234
234
|
|
|
235
235
|
// appear defers via queueMicrotask so all refs are wired first
|
|
236
236
|
await Promise.resolve()
|
|
237
237
|
|
|
238
238
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
239
|
-
expect(wrapperEl.style.height).toBe(
|
|
239
|
+
expect(wrapperEl.style.height).toBe('200px')
|
|
240
240
|
})
|
|
241
241
|
|
|
242
|
-
it(
|
|
242
|
+
it('custom timeout completes leave when transitionend does not fire', () => {
|
|
243
243
|
const show = signal(true)
|
|
244
244
|
const onAfterLeave = vi.fn()
|
|
245
245
|
|
|
@@ -247,7 +247,7 @@ describe("Collapse", () => {
|
|
|
247
247
|
show,
|
|
248
248
|
timeout: 800,
|
|
249
249
|
onAfterLeave,
|
|
250
|
-
children: { type:
|
|
250
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
251
251
|
})
|
|
252
252
|
|
|
253
253
|
show.set(false)
|
|
@@ -258,7 +258,7 @@ describe("Collapse", () => {
|
|
|
258
258
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
259
259
|
})
|
|
260
260
|
|
|
261
|
-
it(
|
|
261
|
+
it('interrupts leave and starts entering when toggled back to show', () => {
|
|
262
262
|
const show = signal(true)
|
|
263
263
|
const onEnter = vi.fn()
|
|
264
264
|
const onLeave = vi.fn()
|
|
@@ -267,7 +267,7 @@ describe("Collapse", () => {
|
|
|
267
267
|
show,
|
|
268
268
|
onEnter,
|
|
269
269
|
onLeave,
|
|
270
|
-
children: { type:
|
|
270
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
271
271
|
})
|
|
272
272
|
|
|
273
273
|
// Start leaving
|
|
@@ -277,10 +277,10 @@ describe("Collapse", () => {
|
|
|
277
277
|
// Toggle back
|
|
278
278
|
show.set(true)
|
|
279
279
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
280
|
-
expect(wrapperEl.style.height).toBe(
|
|
280
|
+
expect(wrapperEl.style.height).toBe('200px')
|
|
281
281
|
})
|
|
282
282
|
|
|
283
|
-
it(
|
|
283
|
+
it('interrupts entering and starts leaving when toggled back to hide', () => {
|
|
284
284
|
const show = signal(false)
|
|
285
285
|
const onEnter = vi.fn()
|
|
286
286
|
const onLeave = vi.fn()
|
|
@@ -289,7 +289,7 @@ describe("Collapse", () => {
|
|
|
289
289
|
show,
|
|
290
290
|
onEnter,
|
|
291
291
|
onLeave,
|
|
292
|
-
children: { type:
|
|
292
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
293
293
|
})
|
|
294
294
|
|
|
295
295
|
// Start entering
|
|
@@ -301,14 +301,14 @@ describe("Collapse", () => {
|
|
|
301
301
|
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
302
302
|
})
|
|
303
303
|
|
|
304
|
-
it(
|
|
304
|
+
it('does not re-trigger entering if already entered', () => {
|
|
305
305
|
const show = signal(false)
|
|
306
306
|
const onEnter = vi.fn()
|
|
307
307
|
|
|
308
308
|
const { wrapperEl } = setupCollapse({
|
|
309
309
|
show,
|
|
310
310
|
onEnter,
|
|
311
|
-
children: { type:
|
|
311
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
312
312
|
})
|
|
313
313
|
|
|
314
314
|
show.set(true)
|
|
@@ -323,14 +323,14 @@ describe("Collapse", () => {
|
|
|
323
323
|
expect(onEnter).toHaveBeenCalledTimes(2)
|
|
324
324
|
})
|
|
325
325
|
|
|
326
|
-
it(
|
|
326
|
+
it('does not re-trigger leaving if already hidden', () => {
|
|
327
327
|
const show = signal(true)
|
|
328
328
|
const onLeave = vi.fn()
|
|
329
329
|
|
|
330
330
|
const { wrapperEl } = setupCollapse({
|
|
331
331
|
show,
|
|
332
332
|
onLeave,
|
|
333
|
-
children: { type:
|
|
333
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
334
334
|
})
|
|
335
335
|
|
|
336
336
|
show.set(false)
|
|
@@ -343,7 +343,7 @@ describe("Collapse", () => {
|
|
|
343
343
|
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
344
344
|
})
|
|
345
345
|
|
|
346
|
-
it(
|
|
346
|
+
it('appear=true fires onAfterEnter after transitionend', async () => {
|
|
347
347
|
const show = signal(true)
|
|
348
348
|
const onAfterEnter = vi.fn()
|
|
349
349
|
|
|
@@ -351,7 +351,7 @@ describe("Collapse", () => {
|
|
|
351
351
|
show,
|
|
352
352
|
appear: true,
|
|
353
353
|
onAfterEnter,
|
|
354
|
-
children: { type:
|
|
354
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
355
355
|
})
|
|
356
356
|
|
|
357
357
|
await Promise.resolve()
|
|
@@ -361,28 +361,28 @@ describe("Collapse", () => {
|
|
|
361
361
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
362
362
|
})
|
|
363
363
|
|
|
364
|
-
it(
|
|
364
|
+
it('leave transition sets height to scrollHeight first then to 0', () => {
|
|
365
365
|
const show = signal(true)
|
|
366
366
|
|
|
367
367
|
const { wrapperEl } = setupCollapse({
|
|
368
368
|
show,
|
|
369
|
-
children: { type:
|
|
369
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
370
370
|
})
|
|
371
371
|
|
|
372
372
|
show.set(false)
|
|
373
373
|
|
|
374
374
|
// After leaving, height should be 0
|
|
375
|
-
expect(wrapperEl.style.height).toBe(
|
|
376
|
-
expect(wrapperEl.style.overflow).toBe(
|
|
377
|
-
expect(wrapperEl.style.transition).toBe(
|
|
375
|
+
expect(wrapperEl.style.height).toBe('0px')
|
|
376
|
+
expect(wrapperEl.style.overflow).toBe('hidden')
|
|
377
|
+
expect(wrapperEl.style.transition).toBe('height 300ms ease')
|
|
378
378
|
})
|
|
379
379
|
})
|
|
380
380
|
|
|
381
381
|
// ─── CollapseRenderer (kinetic mode) ──────────────────────
|
|
382
382
|
|
|
383
383
|
const makeCollapseConfig = (overrides: Partial<KineticConfig> = {}): KineticConfig => ({
|
|
384
|
-
tag:
|
|
385
|
-
mode:
|
|
384
|
+
tag: 'div',
|
|
385
|
+
mode: 'collapse',
|
|
386
386
|
...overrides,
|
|
387
387
|
})
|
|
388
388
|
|
|
@@ -390,9 +390,9 @@ const makeCollapseConfig = (overrides: Partial<KineticConfig> = {}): KineticConf
|
|
|
390
390
|
const wireWrapperRef = (vnode: VNode | null, el: HTMLElement) => {
|
|
391
391
|
if (!vnode?.props) return
|
|
392
392
|
const vnodeProps = vnode.props as Record<string, unknown>
|
|
393
|
-
if (typeof vnodeProps.ref ===
|
|
393
|
+
if (typeof vnodeProps.ref === 'function') {
|
|
394
394
|
;(vnodeProps.ref as (el: HTMLElement | null) => void)(el)
|
|
395
|
-
} else if (vnodeProps.ref && typeof vnodeProps.ref ===
|
|
395
|
+
} else if (vnodeProps.ref && typeof vnodeProps.ref === 'object') {
|
|
396
396
|
;(vnodeProps.ref as { current: HTMLElement | null }).current = el
|
|
397
397
|
}
|
|
398
398
|
}
|
|
@@ -402,17 +402,17 @@ const wireContentRef = (vnode: VNode | null, contentEl: HTMLElement) => {
|
|
|
402
402
|
if (!vnode?.children) return
|
|
403
403
|
const vnodeChildren = Array.isArray(vnode.children) ? vnode.children : [vnode.children]
|
|
404
404
|
for (const c of vnodeChildren) {
|
|
405
|
-
if (!c || typeof c !==
|
|
405
|
+
if (!c || typeof c !== 'object' || !('type' in (c as object))) continue
|
|
406
406
|
const showNode = c as any
|
|
407
407
|
const showChildren = showNode.props?.children ?? showNode.children
|
|
408
408
|
if (!showChildren) continue
|
|
409
409
|
const sc = Array.isArray(showChildren) ? showChildren : [showChildren]
|
|
410
410
|
for (const s of sc) {
|
|
411
|
-
if (!s || typeof s !==
|
|
411
|
+
if (!s || typeof s !== 'object' || !('props' in s)) continue
|
|
412
412
|
const ref = s.props?.ref
|
|
413
|
-
if (ref && typeof ref ===
|
|
413
|
+
if (ref && typeof ref === 'object') {
|
|
414
414
|
ref.current = contentEl
|
|
415
|
-
} else if (typeof ref ===
|
|
415
|
+
} else if (typeof ref === 'function') {
|
|
416
416
|
ref(contentEl)
|
|
417
417
|
}
|
|
418
418
|
}
|
|
@@ -433,10 +433,10 @@ const setupCollapseRenderer = (props: {
|
|
|
433
433
|
callbacks?: Record<string, unknown>
|
|
434
434
|
children?: VNode | VNode[]
|
|
435
435
|
}) => {
|
|
436
|
-
const wrapperEl = document.createElement(
|
|
437
|
-
const contentEl = document.createElement(
|
|
436
|
+
const wrapperEl = document.createElement('div')
|
|
437
|
+
const contentEl = document.createElement('div')
|
|
438
438
|
|
|
439
|
-
Object.defineProperty(wrapperEl,
|
|
439
|
+
Object.defineProperty(wrapperEl, 'offsetHeight', {
|
|
440
440
|
configurable: true,
|
|
441
441
|
get() {
|
|
442
442
|
return 0
|
|
@@ -444,7 +444,7 @@ const setupCollapseRenderer = (props: {
|
|
|
444
444
|
})
|
|
445
445
|
|
|
446
446
|
const config = props.config ?? makeCollapseConfig()
|
|
447
|
-
const child: VNode = { type:
|
|
447
|
+
const child: VNode = { type: 'p', props: {}, children: ['Content'], key: null }
|
|
448
448
|
|
|
449
449
|
const vnode = CollapseRenderer({
|
|
450
450
|
config,
|
|
@@ -463,7 +463,7 @@ const setupCollapseRenderer = (props: {
|
|
|
463
463
|
return { vnode, wrapperEl, contentEl }
|
|
464
464
|
}
|
|
465
465
|
|
|
466
|
-
describe(
|
|
466
|
+
describe('CollapseRenderer', () => {
|
|
467
467
|
beforeEach(() => {
|
|
468
468
|
vi.useFakeTimers()
|
|
469
469
|
mockScrollHeight(200)
|
|
@@ -471,10 +471,10 @@ describe("CollapseRenderer", () => {
|
|
|
471
471
|
|
|
472
472
|
afterEach(() => vi.useRealTimers())
|
|
473
473
|
|
|
474
|
-
it(
|
|
474
|
+
it('returns a VNode with the config.tag', () => {
|
|
475
475
|
const show = signal(true)
|
|
476
|
-
const config = makeCollapseConfig({ tag:
|
|
477
|
-
const child: VNode = { type:
|
|
476
|
+
const config = makeCollapseConfig({ tag: 'section' })
|
|
477
|
+
const child: VNode = { type: 'p', props: {}, children: ['Content'], key: null }
|
|
478
478
|
|
|
479
479
|
const vnode = CollapseRenderer({
|
|
480
480
|
config,
|
|
@@ -485,10 +485,10 @@ describe("CollapseRenderer", () => {
|
|
|
485
485
|
})
|
|
486
486
|
|
|
487
487
|
expect(vnode).not.toBeNull()
|
|
488
|
-
expect(vnode?.type).toBe(
|
|
488
|
+
expect(vnode?.type).toBe('section')
|
|
489
489
|
})
|
|
490
490
|
|
|
491
|
-
it(
|
|
491
|
+
it('fires onEnter and animates height on entering', () => {
|
|
492
492
|
const show = signal(false)
|
|
493
493
|
const onEnter = vi.fn()
|
|
494
494
|
|
|
@@ -499,11 +499,11 @@ describe("CollapseRenderer", () => {
|
|
|
499
499
|
|
|
500
500
|
show.set(true)
|
|
501
501
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
502
|
-
expect(wrapperEl.style.height).toBe(
|
|
503
|
-
expect(wrapperEl.style.transition).toBe(
|
|
502
|
+
expect(wrapperEl.style.height).toBe('200px')
|
|
503
|
+
expect(wrapperEl.style.transition).toBe('height 300ms ease')
|
|
504
504
|
})
|
|
505
505
|
|
|
506
|
-
it(
|
|
506
|
+
it('fires onLeave and animates height to 0 on leaving', () => {
|
|
507
507
|
const show = signal(true)
|
|
508
508
|
const onLeave = vi.fn()
|
|
509
509
|
|
|
@@ -514,11 +514,11 @@ describe("CollapseRenderer", () => {
|
|
|
514
514
|
|
|
515
515
|
show.set(false)
|
|
516
516
|
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
517
|
-
expect(wrapperEl.style.height).toBe(
|
|
518
|
-
expect(wrapperEl.style.overflow).toBe(
|
|
517
|
+
expect(wrapperEl.style.height).toBe('0px')
|
|
518
|
+
expect(wrapperEl.style.overflow).toBe('hidden')
|
|
519
519
|
})
|
|
520
520
|
|
|
521
|
-
it(
|
|
521
|
+
it('fires onAfterEnter and sets height:auto after transitionend', () => {
|
|
522
522
|
const show = signal(false)
|
|
523
523
|
const onAfterEnter = vi.fn()
|
|
524
524
|
|
|
@@ -531,12 +531,12 @@ describe("CollapseRenderer", () => {
|
|
|
531
531
|
fireTransitionEnd(wrapperEl)
|
|
532
532
|
|
|
533
533
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
534
|
-
expect(wrapperEl.style.height).toBe(
|
|
535
|
-
expect(wrapperEl.style.overflow).toBe(
|
|
536
|
-
expect(wrapperEl.style.transition).toBe(
|
|
534
|
+
expect(wrapperEl.style.height).toBe('auto')
|
|
535
|
+
expect(wrapperEl.style.overflow).toBe('')
|
|
536
|
+
expect(wrapperEl.style.transition).toBe('')
|
|
537
537
|
})
|
|
538
538
|
|
|
539
|
-
it(
|
|
539
|
+
it('fires onAfterLeave after leave transitionend', () => {
|
|
540
540
|
const show = signal(true)
|
|
541
541
|
const onAfterLeave = vi.fn()
|
|
542
542
|
|
|
@@ -551,22 +551,22 @@ describe("CollapseRenderer", () => {
|
|
|
551
551
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
552
552
|
})
|
|
553
553
|
|
|
554
|
-
it(
|
|
554
|
+
it('uses custom transition from prop', () => {
|
|
555
555
|
const show = signal(false)
|
|
556
556
|
|
|
557
557
|
const { wrapperEl } = setupCollapseRenderer({
|
|
558
558
|
show: () => show(),
|
|
559
|
-
transition:
|
|
559
|
+
transition: 'height 500ms ease-in-out',
|
|
560
560
|
callbacks: {},
|
|
561
561
|
})
|
|
562
562
|
|
|
563
563
|
show.set(true)
|
|
564
|
-
expect(wrapperEl.style.transition).toBe(
|
|
564
|
+
expect(wrapperEl.style.transition).toBe('height 500ms ease-in-out')
|
|
565
565
|
})
|
|
566
566
|
|
|
567
|
-
it(
|
|
567
|
+
it('uses config.transition as fallback', () => {
|
|
568
568
|
const show = signal(false)
|
|
569
|
-
const config = makeCollapseConfig({ transition:
|
|
569
|
+
const config = makeCollapseConfig({ transition: 'height 700ms linear' })
|
|
570
570
|
|
|
571
571
|
const { wrapperEl } = setupCollapseRenderer({
|
|
572
572
|
config,
|
|
@@ -575,10 +575,10 @@ describe("CollapseRenderer", () => {
|
|
|
575
575
|
})
|
|
576
576
|
|
|
577
577
|
show.set(true)
|
|
578
|
-
expect(wrapperEl.style.transition).toBe(
|
|
578
|
+
expect(wrapperEl.style.transition).toBe('height 700ms linear')
|
|
579
579
|
})
|
|
580
580
|
|
|
581
|
-
it(
|
|
581
|
+
it('appear=true triggers entering via ref proxy on initial mount', async () => {
|
|
582
582
|
const show = signal(true)
|
|
583
583
|
const onEnter = vi.fn()
|
|
584
584
|
|
|
@@ -592,10 +592,10 @@ describe("CollapseRenderer", () => {
|
|
|
592
592
|
await Promise.resolve()
|
|
593
593
|
|
|
594
594
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
595
|
-
expect(wrapperEl.style.height).toBe(
|
|
595
|
+
expect(wrapperEl.style.height).toBe('200px')
|
|
596
596
|
})
|
|
597
597
|
|
|
598
|
-
it(
|
|
598
|
+
it('timeout fallback completes enter when transitionend never fires', () => {
|
|
599
599
|
const show = signal(false)
|
|
600
600
|
const onAfterEnter = vi.fn()
|
|
601
601
|
|
|
@@ -612,7 +612,7 @@ describe("CollapseRenderer", () => {
|
|
|
612
612
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
613
613
|
})
|
|
614
614
|
|
|
615
|
-
it(
|
|
615
|
+
it('timeout fallback completes leave when transitionend never fires', () => {
|
|
616
616
|
const show = signal(true)
|
|
617
617
|
const onAfterLeave = vi.fn()
|
|
618
618
|
|
|
@@ -629,7 +629,7 @@ describe("CollapseRenderer", () => {
|
|
|
629
629
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
630
630
|
})
|
|
631
631
|
|
|
632
|
-
it(
|
|
632
|
+
it('interrupts leave and re-enters when show toggles back', () => {
|
|
633
633
|
const show = signal(true)
|
|
634
634
|
const onEnter = vi.fn()
|
|
635
635
|
const onLeave = vi.fn()
|
|
@@ -644,10 +644,10 @@ describe("CollapseRenderer", () => {
|
|
|
644
644
|
|
|
645
645
|
show.set(true)
|
|
646
646
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
647
|
-
expect(wrapperEl.style.height).toBe(
|
|
647
|
+
expect(wrapperEl.style.height).toBe('200px')
|
|
648
648
|
})
|
|
649
649
|
|
|
650
|
-
it(
|
|
650
|
+
it('uses config.timeout as fallback', () => {
|
|
651
651
|
const show = signal(false)
|
|
652
652
|
const onAfterEnter = vi.fn()
|
|
653
653
|
const config = makeCollapseConfig({ timeout: 400 })
|
|
@@ -663,7 +663,7 @@ describe("CollapseRenderer", () => {
|
|
|
663
663
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
664
664
|
})
|
|
665
665
|
|
|
666
|
-
it(
|
|
666
|
+
it('uses config.appear as fallback', async () => {
|
|
667
667
|
const show = signal(true)
|
|
668
668
|
const onEnter = vi.fn()
|
|
669
669
|
const config = makeCollapseConfig({ appear: true })
|
|
@@ -679,7 +679,7 @@ describe("CollapseRenderer", () => {
|
|
|
679
679
|
})
|
|
680
680
|
})
|
|
681
681
|
|
|
682
|
-
describe(
|
|
682
|
+
describe('CollapseRenderer — reduced motion', () => {
|
|
683
683
|
beforeEach(() => {
|
|
684
684
|
vi.useFakeTimers()
|
|
685
685
|
mockScrollHeight(200)
|
|
@@ -691,7 +691,7 @@ describe("CollapseRenderer — reduced motion", () => {
|
|
|
691
691
|
_reducedMotion = false
|
|
692
692
|
})
|
|
693
693
|
|
|
694
|
-
it(
|
|
694
|
+
it('reduced motion: entering skips animation and sets height:auto immediately', () => {
|
|
695
695
|
const show = signal(false)
|
|
696
696
|
const onEnter = vi.fn()
|
|
697
697
|
const onAfterEnter = vi.fn()
|
|
@@ -705,11 +705,11 @@ describe("CollapseRenderer — reduced motion", () => {
|
|
|
705
705
|
|
|
706
706
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
707
707
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
708
|
-
expect(wrapperEl.style.height).toBe(
|
|
709
|
-
expect(wrapperEl.style.overflow).toBe(
|
|
708
|
+
expect(wrapperEl.style.height).toBe('auto')
|
|
709
|
+
expect(wrapperEl.style.overflow).toBe('')
|
|
710
710
|
})
|
|
711
711
|
|
|
712
|
-
it(
|
|
712
|
+
it('reduced motion: leaving skips animation and sets height:0 immediately', () => {
|
|
713
713
|
const show = signal(true)
|
|
714
714
|
const onLeave = vi.fn()
|
|
715
715
|
const onAfterLeave = vi.fn()
|
|
@@ -723,12 +723,12 @@ describe("CollapseRenderer — reduced motion", () => {
|
|
|
723
723
|
|
|
724
724
|
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
725
725
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
726
|
-
expect(wrapperEl.style.height).toBe(
|
|
727
|
-
expect(wrapperEl.style.overflow).toBe(
|
|
726
|
+
expect(wrapperEl.style.height).toBe('0px')
|
|
727
|
+
expect(wrapperEl.style.overflow).toBe('hidden')
|
|
728
728
|
})
|
|
729
729
|
})
|
|
730
730
|
|
|
731
|
-
describe(
|
|
731
|
+
describe('Collapse — reduced motion', () => {
|
|
732
732
|
beforeEach(() => {
|
|
733
733
|
vi.useFakeTimers()
|
|
734
734
|
mockScrollHeight(200)
|
|
@@ -740,7 +740,7 @@ describe("Collapse — reduced motion", () => {
|
|
|
740
740
|
_reducedMotion = false
|
|
741
741
|
})
|
|
742
742
|
|
|
743
|
-
it(
|
|
743
|
+
it('reduced motion: entering skips animation and fires both callbacks', () => {
|
|
744
744
|
const show = signal(false)
|
|
745
745
|
const onEnter = vi.fn()
|
|
746
746
|
const onAfterEnter = vi.fn()
|
|
@@ -749,18 +749,18 @@ describe("Collapse — reduced motion", () => {
|
|
|
749
749
|
show,
|
|
750
750
|
onEnter,
|
|
751
751
|
onAfterEnter,
|
|
752
|
-
children: { type:
|
|
752
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
753
753
|
})
|
|
754
754
|
|
|
755
755
|
show.set(true)
|
|
756
756
|
|
|
757
757
|
expect(onEnter).toHaveBeenCalledTimes(1)
|
|
758
758
|
expect(onAfterEnter).toHaveBeenCalledTimes(1)
|
|
759
|
-
expect(wrapperEl.style.height).toBe(
|
|
760
|
-
expect(wrapperEl.style.overflow).toBe(
|
|
759
|
+
expect(wrapperEl.style.height).toBe('auto')
|
|
760
|
+
expect(wrapperEl.style.overflow).toBe('')
|
|
761
761
|
})
|
|
762
762
|
|
|
763
|
-
it(
|
|
763
|
+
it('reduced motion: leaving skips animation and fires both callbacks', () => {
|
|
764
764
|
const show = signal(true)
|
|
765
765
|
const onLeave = vi.fn()
|
|
766
766
|
const onAfterLeave = vi.fn()
|
|
@@ -769,14 +769,14 @@ describe("Collapse — reduced motion", () => {
|
|
|
769
769
|
show,
|
|
770
770
|
onLeave,
|
|
771
771
|
onAfterLeave,
|
|
772
|
-
children: { type:
|
|
772
|
+
children: { type: 'div', props: {}, children: ['Hello'], key: undefined },
|
|
773
773
|
})
|
|
774
774
|
|
|
775
775
|
show.set(false)
|
|
776
776
|
|
|
777
777
|
expect(onLeave).toHaveBeenCalledTimes(1)
|
|
778
778
|
expect(onAfterLeave).toHaveBeenCalledTimes(1)
|
|
779
|
-
expect(wrapperEl.style.height).toBe(
|
|
780
|
-
expect(wrapperEl.style.overflow).toBe(
|
|
779
|
+
expect(wrapperEl.style.height).toBe('0px')
|
|
780
|
+
expect(wrapperEl.style.overflow).toBe('hidden')
|
|
781
781
|
})
|
|
782
782
|
})
|