@pyreon/kinetic 0.11.4 → 0.11.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -24
- package/lib/index.d.ts +8 -8
- package/lib/index.js +46 -37
- package/package.json +22 -22
- package/src/Collapse.tsx +44 -44
- package/src/Stagger.tsx +7 -7
- package/src/Transition.tsx +21 -24
- package/src/TransitionGroup.tsx +85 -59
- package/src/__tests__/Collapse.test.tsx +125 -125
- package/src/__tests__/GroupRenderer.test.tsx +233 -190
- 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 +88 -65
- 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
package/README.md
CHANGED
|
@@ -10,15 +10,16 @@ The result: GPU-composited 60/120 FPS animations with a 3.2KB footprint.
|
|
|
10
10
|
|
|
11
11
|
### How It Compares
|
|
12
12
|
|
|
13
|
-
| Library
|
|
14
|
-
|
|
|
15
|
-
| **@pyreon/kinetic**
|
|
16
|
-
| Motion (framer-motion) | ~34 KB
|
|
17
|
-
| @react-spring/web
|
|
18
|
-
| react-transition-group | ~5 KB
|
|
19
|
-
| AutoAnimate
|
|
13
|
+
| Library | Gzipped | Engine | Enter/Exit | Stagger | List Recon. | Collapse | Reduced Motion |
|
|
14
|
+
| ---------------------- | ---------- | ------------------- | ---------- | ------- | ----------- | -------- | -------------- |
|
|
15
|
+
| **@pyreon/kinetic** | **3.2 KB** | CSS transitions | Yes | Yes | Yes | Yes | Yes |
|
|
16
|
+
| Motion (framer-motion) | ~34 KB | JS (rAF + WAAPI) | Yes | Yes | Yes | Quirky | Yes |
|
|
17
|
+
| @react-spring/web | ~16-24 KB | JS (spring physics) | Yes | Partial | Yes | Manual | Yes |
|
|
18
|
+
| react-transition-group | ~5 KB | CSS classes | Yes | No | Yes | No | No |
|
|
19
|
+
| AutoAnimate | ~2.5 KB | JS (FLIP) | Yes | No | Yes | No | Yes |
|
|
20
20
|
|
|
21
21
|
**Key advantages:**
|
|
22
|
+
|
|
22
23
|
- **10x smaller than Motion** for CSS-transition use cases
|
|
23
24
|
- **CSS-first**: `transform`/`opacity` run on GPU compositor thread, not main thread
|
|
24
25
|
- **Only library** combining CSS transitions + stagger + collapse + list reconciliation
|
|
@@ -53,9 +54,9 @@ FadeDiv({ show: show(), children: 'Hello, world!' })
|
|
|
53
54
|
Creates an animated component. `tag` can be any HTML element string or Pyreon component.
|
|
54
55
|
|
|
55
56
|
```ts
|
|
56
|
-
kinetic('div')
|
|
57
|
-
kinetic('section')
|
|
58
|
-
kinetic(MyComponent)
|
|
57
|
+
kinetic('div') // HTML element
|
|
58
|
+
kinetic('section') // Any HTML tag
|
|
59
|
+
kinetic(MyComponent) // Pyreon component
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
Returns a renderable Pyreon component with chain methods attached. Default mode: **transition**.
|
|
@@ -109,7 +110,7 @@ Height animation with `overflow: hidden`. Measures `scrollHeight` automatically.
|
|
|
109
110
|
```ts
|
|
110
111
|
const Accordion = kinetic('div').collapse()
|
|
111
112
|
const FancyAccordion = kinetic('section').collapse({
|
|
112
|
-
transition: 'height 400ms cubic-bezier(0.4, 0, 0.2, 1)'
|
|
113
|
+
transition: 'height 400ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
113
114
|
})
|
|
114
115
|
|
|
115
116
|
Accordion({ show: isExpanded, children: 'Expandable content' })
|
|
@@ -122,11 +123,14 @@ Staggered entrance/exit for child elements.
|
|
|
122
123
|
```ts
|
|
123
124
|
const StaggerList = kinetic('ul').preset(slideUp).stagger({ interval: 75 })
|
|
124
125
|
|
|
125
|
-
StaggerList({
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
StaggerList({
|
|
127
|
+
show: isVisible,
|
|
128
|
+
children: [
|
|
129
|
+
h('li', { key: '1' }, 'Item 1'),
|
|
130
|
+
h('li', { key: '2' }, 'Item 2'),
|
|
131
|
+
h('li', { key: '3' }, 'Item 3'),
|
|
132
|
+
],
|
|
133
|
+
})
|
|
130
134
|
```
|
|
131
135
|
|
|
132
136
|
#### Group
|
|
@@ -136,9 +140,7 @@ Key-based enter/exit — adding a child triggers enter animation, removing trigg
|
|
|
136
140
|
```ts
|
|
137
141
|
const AnimatedList = kinetic('ul').preset(fade).group()
|
|
138
142
|
|
|
139
|
-
AnimatedList({ children: items.map(item =>
|
|
140
|
-
h('li', { key: item.id }, item.text)
|
|
141
|
-
)})
|
|
143
|
+
AnimatedList({ children: items.map((item) => h('li', { key: item.id }, item.text)) })
|
|
142
144
|
```
|
|
143
145
|
|
|
144
146
|
### Inline Configuration
|
|
@@ -199,8 +201,9 @@ Kinetic and rocketstyle compose naturally:
|
|
|
199
201
|
```ts
|
|
200
202
|
import rocketstyle from '@pyreon/rocketstyle'
|
|
201
203
|
|
|
202
|
-
const Button = rocketstyle()({ component: 'button', name: 'Button' })
|
|
203
|
-
|
|
204
|
+
const Button = rocketstyle()({ component: 'button', name: 'Button' }).theme({
|
|
205
|
+
primaryColor: 'blue',
|
|
206
|
+
})
|
|
204
207
|
|
|
205
208
|
const AnimatedButton = kinetic(Button).preset(fade)
|
|
206
209
|
|
|
@@ -210,9 +213,9 @@ AnimatedButton({ show: isVisible, primary: true, size: 'large', children: 'Click
|
|
|
210
213
|
|
|
211
214
|
## Peer Dependencies
|
|
212
215
|
|
|
213
|
-
| Package
|
|
214
|
-
|
|
|
215
|
-
| @pyreon/core
|
|
216
|
+
| Package | Version |
|
|
217
|
+
| ------------------ | -------- |
|
|
218
|
+
| @pyreon/core | >= 0.0.1 |
|
|
216
219
|
| @pyreon/reactivity | >= 0.0.1 |
|
|
217
220
|
|
|
218
221
|
## License
|
package/lib/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Signal } from "@pyreon/reactivity";
|
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
type CSSProperties = Record<string, string | number | undefined>;
|
|
6
6
|
/** Internal lifecycle stages of a transition. */
|
|
7
|
-
type TransitionStage =
|
|
7
|
+
type TransitionStage = 'hidden' | 'entering' | 'entered' | 'leaving';
|
|
8
8
|
/** Class-based transition definition. */
|
|
9
9
|
type ClassTransitionProps = {
|
|
10
10
|
/** Classes applied during the entire enter phase */enter?: string | undefined; /** Classes applied on first frame of enter, removed on next frame */
|
|
@@ -38,7 +38,7 @@ type TransitionStateResult = {
|
|
|
38
38
|
};
|
|
39
39
|
//#endregion
|
|
40
40
|
//#region src/kinetic/types.d.ts
|
|
41
|
-
type KineticMode =
|
|
41
|
+
type KineticMode = 'transition' | 'collapse' | 'stagger' | 'group';
|
|
42
42
|
type ClassConfig = {
|
|
43
43
|
active?: string | undefined;
|
|
44
44
|
from?: string | undefined;
|
|
@@ -91,8 +91,8 @@ type KineticGroupProps<_Tag extends string> = Record<string, unknown> & {
|
|
|
91
91
|
timeout?: number | undefined;
|
|
92
92
|
children: VNodeChild;
|
|
93
93
|
} & Partial<TransitionCallbacks>;
|
|
94
|
-
type KineticComponentProps<Tag extends string, Mode extends KineticMode> = Mode extends
|
|
95
|
-
type ConfigOpts<Mode extends KineticMode> = Mode extends
|
|
94
|
+
type KineticComponentProps<Tag extends string, Mode extends KineticMode> = Mode extends 'collapse' ? KineticCollapseProps<Tag> : Mode extends 'stagger' ? KineticStaggerProps<Tag> : Mode extends 'group' ? KineticGroupProps<Tag> : KineticTransitionProps<Tag>;
|
|
95
|
+
type ConfigOpts<Mode extends KineticMode> = Mode extends 'collapse' ? CollapseConfigOpts : Mode extends 'stagger' ? StaggerConfigOpts : Mode extends 'group' ? GroupConfigOpts : TransitionConfigOpts;
|
|
96
96
|
type KineticChain<Tag extends string, Mode extends KineticMode> = {
|
|
97
97
|
displayName: string;
|
|
98
98
|
preset: (preset: StyleTransitionProps & ClassTransitionProps) => KineticComponent<Tag, Mode>;
|
|
@@ -106,14 +106,14 @@ type KineticChain<Tag extends string, Mode extends KineticMode> = {
|
|
|
106
106
|
leaveClass: (opts: ClassConfig) => KineticComponent<Tag, Mode>;
|
|
107
107
|
config: (opts: ConfigOpts<Mode>) => KineticComponent<Tag, Mode>;
|
|
108
108
|
on: (callbacks: Partial<TransitionCallbacks>) => KineticComponent<Tag, Mode>;
|
|
109
|
-
collapse: (opts?: CollapseConfigOpts) => KineticComponent<Tag,
|
|
109
|
+
collapse: (opts?: CollapseConfigOpts) => KineticComponent<Tag, 'collapse'>;
|
|
110
110
|
stagger: (opts?: {
|
|
111
111
|
interval?: number | undefined;
|
|
112
112
|
reverseLeave?: boolean | undefined;
|
|
113
|
-
}) => KineticComponent<Tag,
|
|
114
|
-
group: () => KineticComponent<Tag,
|
|
113
|
+
}) => KineticComponent<Tag, 'stagger'>;
|
|
114
|
+
group: () => KineticComponent<Tag, 'group'>;
|
|
115
115
|
};
|
|
116
|
-
type KineticComponent<Tag extends string, Mode extends KineticMode =
|
|
116
|
+
type KineticComponent<Tag extends string, Mode extends KineticMode = 'transition'> = ComponentFn<KineticComponentProps<Tag, Mode>> & KineticChain<Tag, Mode>;
|
|
117
117
|
//#endregion
|
|
118
118
|
//#region src/kinetic.d.ts
|
|
119
119
|
/**
|
package/lib/index.js
CHANGED
|
@@ -415,55 +415,64 @@ const getKeyedChildren = (children) => {
|
|
|
415
415
|
* Children that appear (new key) animate in. Children that disappear
|
|
416
416
|
* (removed key) stay in DOM during leave animation, then unmount.
|
|
417
417
|
* config.tag wraps all children as a container element.
|
|
418
|
+
*
|
|
419
|
+
* In Pyreon, components run once. Pass children as a reactive accessor
|
|
420
|
+
* `() => VNode[]` for the group to detect changes and animate entries/exits.
|
|
418
421
|
*/
|
|
419
422
|
const GroupRenderer = ({ config, htmlProps, appear, timeout, callbacks, children }) => {
|
|
420
423
|
const effectiveAppear = appear ?? config.appear ?? false;
|
|
421
424
|
const effectiveTimeout = timeout ?? config.timeout ?? 5e3;
|
|
422
425
|
const prevMap = /* @__PURE__ */ new Map();
|
|
423
426
|
const leavingMap = /* @__PURE__ */ new Map();
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
for (const [key, child] of prevMap) if (!currentMap.has(key)) leavingMap.set(key, child);
|
|
430
|
-
for (const key of currentMap.keys()) leavingMap.delete(key);
|
|
431
|
-
prevMap.clear();
|
|
432
|
-
for (const [key, element] of currentMap) prevMap.set(key, element);
|
|
427
|
+
const forceUpdate = signal(0);
|
|
428
|
+
const getChildren = typeof children === "function" ? children : () => children;
|
|
429
|
+
const initialKeyed = getKeyedChildren(getChildren());
|
|
430
|
+
const initialKeys = new Set(initialKeyed.map((c) => c.key));
|
|
431
|
+
for (const { key, element } of initialKeyed) prevMap.set(key, element);
|
|
433
432
|
const handleAfterLeave = (key) => {
|
|
434
433
|
leavingMap.delete(key);
|
|
435
434
|
callbacks.onAfterLeave?.();
|
|
436
|
-
|
|
435
|
+
forceUpdate.update((c) => c + 1);
|
|
437
436
|
};
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
437
|
+
return (() => {
|
|
438
|
+
forceUpdate();
|
|
439
|
+
const currentKeyed = getKeyedChildren(getChildren());
|
|
440
|
+
const currentMap = /* @__PURE__ */ new Map();
|
|
441
|
+
for (const { key, element } of currentKeyed) currentMap.set(key, element);
|
|
442
|
+
for (const [key, child] of prevMap) if (!currentMap.has(key) && !leavingMap.has(key)) leavingMap.set(key, child);
|
|
443
|
+
for (const key of currentMap.keys()) leavingMap.delete(key);
|
|
444
|
+
prevMap.clear();
|
|
445
|
+
for (const [key, element] of currentMap) prevMap.set(key, element);
|
|
446
|
+
const allEntries = [...currentKeyed];
|
|
447
|
+
for (const [key, element] of leavingMap) allEntries.push({
|
|
448
|
+
key,
|
|
449
|
+
element
|
|
450
|
+
});
|
|
451
|
+
const groupedChildren = allEntries.map(({ key, element }) => {
|
|
452
|
+
const isInitial = initialKeys.has(key);
|
|
453
|
+
const isShowing = currentMap.has(key);
|
|
454
|
+
return /* @__PURE__ */ jsx(TransitionItem, {
|
|
455
|
+
show: () => isShowing,
|
|
456
|
+
appear: isInitial ? effectiveAppear : true,
|
|
457
|
+
timeout: effectiveTimeout,
|
|
458
|
+
enterStyle: config.enterStyle,
|
|
459
|
+
enterToStyle: config.enterToStyle,
|
|
460
|
+
enterTransition: config.enterTransition,
|
|
461
|
+
leaveStyle: config.leaveStyle,
|
|
462
|
+
leaveToStyle: config.leaveToStyle,
|
|
463
|
+
leaveTransition: config.leaveTransition,
|
|
464
|
+
enter: config.enter,
|
|
465
|
+
enterFrom: config.enterFrom,
|
|
466
|
+
enterTo: config.enterTo,
|
|
467
|
+
leave: config.leave,
|
|
468
|
+
leaveFrom: config.leaveFrom,
|
|
469
|
+
leaveTo: config.leaveTo,
|
|
470
|
+
onAfterLeave: () => handleAfterLeave(key),
|
|
471
|
+
children: element
|
|
472
|
+
});
|
|
464
473
|
});
|
|
474
|
+
return h(config.tag, { ...htmlProps }, ...groupedChildren);
|
|
465
475
|
});
|
|
466
|
-
return h(config.tag, { ...htmlProps }, ...groupedChildren);
|
|
467
476
|
};
|
|
468
477
|
|
|
469
478
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/kinetic",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.6",
|
|
4
|
+
"description": "CSS-transition-based animation components for Pyreon",
|
|
5
|
+
"license": "MIT",
|
|
4
6
|
"repository": {
|
|
5
7
|
"type": "git",
|
|
6
8
|
"url": "https://github.com/pyreon/pyreon",
|
|
7
9
|
"directory": "packages/ui-system/kinetic"
|
|
8
10
|
},
|
|
9
|
-
"description": "CSS-transition-based animation components for Pyreon",
|
|
10
|
-
"license": "MIT",
|
|
11
|
-
"type": "module",
|
|
12
|
-
"sideEffects": false,
|
|
13
|
-
"exports": {
|
|
14
|
-
".": {
|
|
15
|
-
"bun": "./src/index.ts",
|
|
16
|
-
"import": "./lib/index.js",
|
|
17
|
-
"types": "./lib/index.d.ts"
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"types": "./lib/index.d.ts",
|
|
21
|
-
"main": "./lib/index.js",
|
|
22
11
|
"files": [
|
|
23
12
|
"lib",
|
|
24
13
|
"!lib/**/*.map",
|
|
@@ -27,8 +16,16 @@
|
|
|
27
16
|
"LICENSE",
|
|
28
17
|
"src"
|
|
29
18
|
],
|
|
30
|
-
"
|
|
31
|
-
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"main": "./lib/index.js",
|
|
22
|
+
"types": "./lib/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"bun": "./src/index.ts",
|
|
26
|
+
"import": "./lib/index.js",
|
|
27
|
+
"types": "./lib/index.d.ts"
|
|
28
|
+
}
|
|
32
29
|
},
|
|
33
30
|
"publishConfig": {
|
|
34
31
|
"access": "public"
|
|
@@ -37,18 +34,21 @@
|
|
|
37
34
|
"prepublish": "bun run build",
|
|
38
35
|
"build": "bun run vl_rolldown_build",
|
|
39
36
|
"build:watch": "bun run vl_rolldown_build-watch",
|
|
40
|
-
"lint": "
|
|
37
|
+
"lint": "oxlint .",
|
|
41
38
|
"test": "vitest run",
|
|
42
39
|
"test:coverage": "vitest run --coverage",
|
|
43
40
|
"test:watch": "vitest",
|
|
44
41
|
"typecheck": "tsc --noEmit"
|
|
45
42
|
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@pyreon/typescript": "^0.11.6",
|
|
45
|
+
"@vitus-labs/tools-rolldown": "^1.15.3"
|
|
46
|
+
},
|
|
46
47
|
"peerDependencies": {
|
|
47
|
-
"@pyreon/core": "^0.11.
|
|
48
|
-
"@pyreon/reactivity": "^0.11.
|
|
48
|
+
"@pyreon/core": "^0.11.6",
|
|
49
|
+
"@pyreon/reactivity": "^0.11.6"
|
|
49
50
|
},
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"@pyreon/typescript": "^0.11.4"
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">= 22"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/src/Collapse.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { VNode } from
|
|
2
|
-
import { createRef, Show } from
|
|
3
|
-
import { runUntracked, signal, watch } from
|
|
4
|
-
import type { CollapseProps, TransitionStage } from
|
|
5
|
-
import useAnimationEnd from
|
|
6
|
-
import { useReducedMotion } from
|
|
1
|
+
import type { VNode } from '@pyreon/core'
|
|
2
|
+
import { createRef, Show } from '@pyreon/core'
|
|
3
|
+
import { runUntracked, signal, watch } from '@pyreon/reactivity'
|
|
4
|
+
import type { CollapseProps, TransitionStage } from './types'
|
|
5
|
+
import useAnimationEnd from './useAnimationEnd'
|
|
6
|
+
import { useReducedMotion } from './useReducedMotion'
|
|
7
7
|
|
|
8
8
|
const Collapse = ({
|
|
9
9
|
show,
|
|
10
|
-
transition =
|
|
10
|
+
transition = 'height 300ms ease',
|
|
11
11
|
appear = false,
|
|
12
12
|
timeout = 5000,
|
|
13
13
|
onEnter,
|
|
@@ -30,7 +30,7 @@ const Collapse = ({
|
|
|
30
30
|
const initialShow = show()
|
|
31
31
|
// When appear=true and show starts true, mount but defer animation until ref is wired
|
|
32
32
|
const needsAppear = appear && initialShow
|
|
33
|
-
const stage = signal<TransitionStage>(initialShow ?
|
|
33
|
+
const stage = signal<TransitionStage>(initialShow ? 'entered' : 'hidden')
|
|
34
34
|
let isInitialMount = true
|
|
35
35
|
let appearTriggered = false
|
|
36
36
|
|
|
@@ -39,7 +39,7 @@ const Collapse = ({
|
|
|
39
39
|
if (needsAppear) {
|
|
40
40
|
const orig = wrapperRef
|
|
41
41
|
const proxy = { current: null as HTMLDivElement | null }
|
|
42
|
-
Object.defineProperty(proxy,
|
|
42
|
+
Object.defineProperty(proxy, 'current', {
|
|
43
43
|
get() {
|
|
44
44
|
return orig.current
|
|
45
45
|
},
|
|
@@ -47,7 +47,7 @@ const Collapse = ({
|
|
|
47
47
|
orig.current = node
|
|
48
48
|
if (node && !appearTriggered) {
|
|
49
49
|
appearTriggered = true
|
|
50
|
-
queueMicrotask(() => stage.set(
|
|
50
|
+
queueMicrotask(() => stage.set('entering'))
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
})
|
|
@@ -65,10 +65,10 @@ const Collapse = ({
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
const currentStage = runUntracked(() => stage())
|
|
68
|
-
if (showVal && (currentStage ===
|
|
69
|
-
stage.set(
|
|
70
|
-
} else if (!showVal && (currentStage ===
|
|
71
|
-
stage.set(
|
|
68
|
+
if (showVal && (currentStage === 'hidden' || currentStage === 'leaving')) {
|
|
69
|
+
stage.set('entering')
|
|
70
|
+
} else if (!showVal && (currentStage === 'entered' || currentStage === 'entering')) {
|
|
71
|
+
stage.set('leaving')
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
{ immediate: true },
|
|
@@ -83,44 +83,44 @@ const Collapse = ({
|
|
|
83
83
|
if (!wrapper || !content) return
|
|
84
84
|
|
|
85
85
|
if (reducedMotion()) {
|
|
86
|
-
if (currentStage ===
|
|
86
|
+
if (currentStage === 'entering') {
|
|
87
87
|
callbacks.onEnter?.()
|
|
88
|
-
wrapper.style.height =
|
|
89
|
-
wrapper.style.overflow =
|
|
88
|
+
wrapper.style.height = 'auto'
|
|
89
|
+
wrapper.style.overflow = ''
|
|
90
90
|
callbacks.onAfterEnter?.()
|
|
91
|
-
stage.set(
|
|
92
|
-
} else if (currentStage ===
|
|
91
|
+
stage.set('entered')
|
|
92
|
+
} else if (currentStage === 'leaving') {
|
|
93
93
|
callbacks.onLeave?.()
|
|
94
|
-
wrapper.style.height =
|
|
95
|
-
wrapper.style.overflow =
|
|
94
|
+
wrapper.style.height = '0px'
|
|
95
|
+
wrapper.style.overflow = 'hidden'
|
|
96
96
|
callbacks.onAfterLeave?.()
|
|
97
|
-
stage.set(
|
|
97
|
+
stage.set('hidden')
|
|
98
98
|
}
|
|
99
99
|
return
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
if (currentStage ===
|
|
102
|
+
if (currentStage === 'entering') {
|
|
103
103
|
callbacks.onEnter?.()
|
|
104
104
|
const height = content.scrollHeight
|
|
105
|
-
wrapper.style.transition =
|
|
106
|
-
wrapper.style.height =
|
|
107
|
-
wrapper.style.overflow =
|
|
105
|
+
wrapper.style.transition = 'none'
|
|
106
|
+
wrapper.style.height = '0px'
|
|
107
|
+
wrapper.style.overflow = 'hidden'
|
|
108
108
|
// Force reflow so the browser registers height: 0
|
|
109
109
|
void wrapper.offsetHeight
|
|
110
110
|
wrapper.style.transition = transition
|
|
111
111
|
wrapper.style.height = `${height}px`
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
if (currentStage ===
|
|
114
|
+
if (currentStage === 'leaving') {
|
|
115
115
|
callbacks.onLeave?.()
|
|
116
116
|
const height = content.scrollHeight
|
|
117
|
-
wrapper.style.transition =
|
|
117
|
+
wrapper.style.transition = 'none'
|
|
118
118
|
wrapper.style.height = `${height}px`
|
|
119
|
-
wrapper.style.overflow =
|
|
119
|
+
wrapper.style.overflow = 'hidden'
|
|
120
120
|
// Force reflow
|
|
121
121
|
void wrapper.offsetHeight
|
|
122
122
|
wrapper.style.transition = transition
|
|
123
|
-
wrapper.style.height =
|
|
123
|
+
wrapper.style.height = '0px'
|
|
124
124
|
}
|
|
125
125
|
},
|
|
126
126
|
{ immediate: true },
|
|
@@ -129,36 +129,36 @@ const Collapse = ({
|
|
|
129
129
|
// Listen for animation end
|
|
130
130
|
useAnimationEnd({
|
|
131
131
|
ref: wrapperRef,
|
|
132
|
-
active: () => (stage() ===
|
|
132
|
+
active: () => (stage() === 'entering' || stage() === 'leaving') && !reducedMotion(),
|
|
133
133
|
timeout,
|
|
134
134
|
onEnd: () => {
|
|
135
135
|
const wrapper = wrapperRef.current
|
|
136
|
-
if (stage() ===
|
|
136
|
+
if (stage() === 'entering') {
|
|
137
137
|
if (wrapper) {
|
|
138
|
-
wrapper.style.height =
|
|
139
|
-
wrapper.style.overflow =
|
|
140
|
-
wrapper.style.transition =
|
|
138
|
+
wrapper.style.height = 'auto'
|
|
139
|
+
wrapper.style.overflow = ''
|
|
140
|
+
wrapper.style.transition = ''
|
|
141
141
|
}
|
|
142
142
|
callbacks.onAfterEnter?.()
|
|
143
|
-
stage.set(
|
|
144
|
-
} else if (stage() ===
|
|
143
|
+
stage.set('entered')
|
|
144
|
+
} else if (stage() === 'leaving') {
|
|
145
145
|
callbacks.onAfterLeave?.()
|
|
146
|
-
stage.set(
|
|
146
|
+
stage.set('hidden')
|
|
147
147
|
}
|
|
148
148
|
},
|
|
149
149
|
})
|
|
150
150
|
|
|
151
|
-
const shouldRender = () => stage() !==
|
|
151
|
+
const shouldRender = () => stage() !== 'hidden'
|
|
152
152
|
|
|
153
153
|
return (
|
|
154
154
|
<div
|
|
155
155
|
ref={wrapperRef}
|
|
156
156
|
style={{
|
|
157
|
-
...(stage() !==
|
|
158
|
-
...(stage() ===
|
|
159
|
-
? { height:
|
|
160
|
-
: stage() ===
|
|
161
|
-
? { height:
|
|
157
|
+
...(stage() !== 'entered' ? { overflow: 'hidden' } : {}),
|
|
158
|
+
...(stage() === 'hidden'
|
|
159
|
+
? { height: '0px' }
|
|
160
|
+
: stage() === 'entered'
|
|
161
|
+
? { height: 'auto' }
|
|
162
162
|
: {}),
|
|
163
163
|
}}
|
|
164
164
|
>
|
package/src/Stagger.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { VNode } from
|
|
2
|
-
import Transition from
|
|
3
|
-
import type { CSSProperties, StaggerProps } from
|
|
4
|
-
import { cloneVNode } from
|
|
1
|
+
import type { VNode } from '@pyreon/core'
|
|
2
|
+
import Transition from './Transition'
|
|
3
|
+
import type { CSSProperties, StaggerProps } from './types'
|
|
4
|
+
import { cloneVNode } from './utils'
|
|
5
5
|
|
|
6
6
|
const isVNode = (child: unknown): child is VNode =>
|
|
7
|
-
child != null && typeof child ===
|
|
7
|
+
child != null && typeof child === 'object' && 'type' in (child as object)
|
|
8
8
|
|
|
9
9
|
const Stagger = ({
|
|
10
10
|
show,
|
|
@@ -37,8 +37,8 @@ const Stagger = ({
|
|
|
37
37
|
{cloneVNode(child, {
|
|
38
38
|
style: {
|
|
39
39
|
...((child.props as Record<string, unknown>)?.style as CSSProperties | undefined),
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
'--stagger-index': staggerIndex,
|
|
41
|
+
'--stagger-interval': `${interval}ms`,
|
|
42
42
|
transitionDelay: `${delay}ms`,
|
|
43
43
|
} as CSSProperties,
|
|
44
44
|
})}
|
package/src/Transition.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { VNode } from
|
|
2
|
-
import { createRef, Show } from
|
|
3
|
-
import { watch } from
|
|
4
|
-
import type { ClassTransitionProps, StyleTransitionProps, TransitionProps } from
|
|
5
|
-
import useAnimationEnd from
|
|
6
|
-
import { useReducedMotion } from
|
|
7
|
-
import useTransitionState from
|
|
8
|
-
import { addClasses, cloneVNode, mergeRefs, mergeStyles, nextFrame, removeClasses } from
|
|
1
|
+
import type { VNode } from '@pyreon/core'
|
|
2
|
+
import { createRef, Show } from '@pyreon/core'
|
|
3
|
+
import { watch } from '@pyreon/reactivity'
|
|
4
|
+
import type { ClassTransitionProps, StyleTransitionProps, TransitionProps } from './types'
|
|
5
|
+
import useAnimationEnd from './useAnimationEnd'
|
|
6
|
+
import { useReducedMotion } from './useReducedMotion'
|
|
7
|
+
import useTransitionState from './useTransitionState'
|
|
8
|
+
import { addClasses, cloneVNode, mergeRefs, mergeStyles, nextFrame, removeClasses } from './utils'
|
|
9
9
|
|
|
10
10
|
const applyEnter = (
|
|
11
11
|
el: HTMLElement,
|
|
@@ -68,11 +68,11 @@ const applyReducedMotion = (
|
|
|
68
68
|
},
|
|
69
69
|
complete: () => void,
|
|
70
70
|
) => {
|
|
71
|
-
if (stage ===
|
|
71
|
+
if (stage === 'entering') {
|
|
72
72
|
callbacks.onEnter?.()
|
|
73
73
|
callbacks.onAfterEnter?.()
|
|
74
74
|
complete()
|
|
75
|
-
} else if (stage ===
|
|
75
|
+
} else if (stage === 'leaving') {
|
|
76
76
|
callbacks.onLeave?.()
|
|
77
77
|
callbacks.onAfterLeave?.()
|
|
78
78
|
complete()
|
|
@@ -114,12 +114,11 @@ const Transition = ({
|
|
|
114
114
|
})
|
|
115
115
|
|
|
116
116
|
const elementRef = createRef<HTMLElement>()
|
|
117
|
+
const childProps = (children.props ?? {}) as Record<string, unknown>
|
|
117
118
|
const mergedRef = mergeRefs(
|
|
118
119
|
elementRef,
|
|
119
120
|
stateRef,
|
|
120
|
-
|
|
121
|
-
| ((el: HTMLElement | null) => void)
|
|
122
|
-
| undefined,
|
|
121
|
+
childProps.ref as ((el: HTMLElement | null) => void) | undefined,
|
|
123
122
|
)
|
|
124
123
|
|
|
125
124
|
const callbacks = {
|
|
@@ -146,12 +145,12 @@ const Transition = ({
|
|
|
146
145
|
|
|
147
146
|
useAnimationEnd({
|
|
148
147
|
ref: elementRef,
|
|
149
|
-
active: () => (stage() ===
|
|
148
|
+
active: () => (stage() === 'entering' || stage() === 'leaving') && !reducedMotion(),
|
|
150
149
|
timeout,
|
|
151
150
|
onEnd: () => {
|
|
152
|
-
if (stage() ===
|
|
151
|
+
if (stage() === 'entering') {
|
|
153
152
|
callbacks.onAfterEnter?.()
|
|
154
|
-
} else if (stage() ===
|
|
153
|
+
} else if (stage() === 'leaving') {
|
|
155
154
|
callbacks.onAfterLeave?.()
|
|
156
155
|
}
|
|
157
156
|
complete()
|
|
@@ -169,21 +168,21 @@ const Transition = ({
|
|
|
169
168
|
return
|
|
170
169
|
}
|
|
171
170
|
|
|
172
|
-
if (currentStage ===
|
|
171
|
+
if (currentStage === 'entering') {
|
|
173
172
|
callbacks.onEnter?.()
|
|
174
173
|
const frameId = applyEnter(el, transitionConfig)
|
|
175
174
|
return () => cancelAnimationFrame(frameId)
|
|
176
175
|
}
|
|
177
176
|
|
|
178
|
-
if (currentStage ===
|
|
177
|
+
if (currentStage === 'leaving') {
|
|
179
178
|
callbacks.onLeave?.()
|
|
180
179
|
const frameId = applyLeave(el, transitionConfig)
|
|
181
180
|
return () => cancelAnimationFrame(frameId)
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
if (currentStage ===
|
|
183
|
+
if (currentStage === 'entered') {
|
|
185
184
|
removeClasses(el, enter)
|
|
186
|
-
el.style.transition =
|
|
185
|
+
el.style.transition = ''
|
|
187
186
|
}
|
|
188
187
|
},
|
|
189
188
|
{ immediate: true },
|
|
@@ -198,10 +197,8 @@ const Transition = ({
|
|
|
198
197
|
: cloneVNode(children, {
|
|
199
198
|
ref: mergedRef,
|
|
200
199
|
style: mergeStyles(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
| undefined,
|
|
204
|
-
{ display: "none" },
|
|
200
|
+
childProps.style as Record<string, string | number | undefined> | undefined,
|
|
201
|
+
{ display: 'none' },
|
|
205
202
|
),
|
|
206
203
|
})
|
|
207
204
|
}
|