@pyreon/kinetic 0.0.2
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/LICENSE +21 -0
- package/README.md +220 -0
- package/lib/index.d.ts +173 -0
- package/lib/index.js +903 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present Vit Bokisch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# @pyreon/kinetic
|
|
2
|
+
|
|
3
|
+
CSS-first animation library for Pyreon. Enter/exit transitions, staggered animations, height collapse, and list reconciliation — all in ~3KB gzipped.
|
|
4
|
+
|
|
5
|
+
## Why Kinetic?
|
|
6
|
+
|
|
7
|
+
Most animation libraries run their own JavaScript animation loop on the main thread. Kinetic takes a different approach: it delegates all interpolation to the browser's CSS transition engine (compositor thread for `transform`/`opacity`), and only handles orchestration — mount/unmount lifecycle, stagger timing, height measurement, and list diffing.
|
|
8
|
+
|
|
9
|
+
The result: GPU-composited 60/120 FPS animations with a 3.2KB footprint.
|
|
10
|
+
|
|
11
|
+
### How It Compares
|
|
12
|
+
|
|
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
|
+
|
|
21
|
+
**Key advantages:**
|
|
22
|
+
- **10x smaller than Motion** for CSS-transition use cases
|
|
23
|
+
- **CSS-first**: `transform`/`opacity` run on GPU compositor thread, not main thread
|
|
24
|
+
- **Only library** combining CSS transitions + stagger + collapse + list reconciliation
|
|
25
|
+
- **122 presets** available via `@pyreon/kinetic-presets`
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bun add @pyreon/kinetic
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { kinetic, fade, slideUp } from '@pyreon/kinetic'
|
|
37
|
+
import { signal } from '@pyreon/reactivity'
|
|
38
|
+
|
|
39
|
+
// Create animated components at module level
|
|
40
|
+
const FadeDiv = kinetic('div').preset(fade)
|
|
41
|
+
const SlideSection = kinetic('section').preset(slideUp)
|
|
42
|
+
|
|
43
|
+
// Use with signals for reactive show/hide
|
|
44
|
+
const show = signal(true)
|
|
45
|
+
|
|
46
|
+
FadeDiv({ show: show(), children: 'Hello, world!' })
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
### `kinetic(tag)`
|
|
52
|
+
|
|
53
|
+
Creates an animated component. `tag` can be any HTML element string or Pyreon component.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
kinetic('div') // HTML element
|
|
57
|
+
kinetic('section') // Any HTML tag
|
|
58
|
+
kinetic(MyComponent) // Pyreon component
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Returns a renderable Pyreon component with chain methods attached. Default mode: **transition**.
|
|
62
|
+
|
|
63
|
+
### Chain Methods
|
|
64
|
+
|
|
65
|
+
All methods return a new component (immutable). The tag generic flows through, preserving HTML attribute types.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// Style-based animation config
|
|
69
|
+
.enter(styles) // CSSProperties applied at enter start
|
|
70
|
+
.enterTo(styles) // CSSProperties applied after first frame
|
|
71
|
+
.enterTransition(value) // CSS transition string for enter
|
|
72
|
+
.leave(styles) // CSSProperties applied at leave start
|
|
73
|
+
.leaveTo(styles) // CSSProperties applied after first frame
|
|
74
|
+
.leaveTransition(value) // CSS transition string for leave
|
|
75
|
+
|
|
76
|
+
// Class-based animation config
|
|
77
|
+
.enterClass({ active?, from?, to? })
|
|
78
|
+
.leaveClass({ active?, from?, to? })
|
|
79
|
+
|
|
80
|
+
// Apply a preset (spreads style + class props)
|
|
81
|
+
.preset(preset)
|
|
82
|
+
|
|
83
|
+
// Behavior config
|
|
84
|
+
.config(opts) // appear, unmount, timeout (+ mode-specific)
|
|
85
|
+
.on(callbacks) // onEnter, onAfterEnter, onLeave, onAfterLeave
|
|
86
|
+
|
|
87
|
+
// Mode switches
|
|
88
|
+
.collapse(opts?) // Height animation mode
|
|
89
|
+
.stagger(opts?) // Staggered children mode
|
|
90
|
+
.group() // Key-based list reconciliation mode
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Four Modes
|
|
94
|
+
|
|
95
|
+
#### Transition (default)
|
|
96
|
+
|
|
97
|
+
Single element enter/leave with CSS transitions.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const FadeDiv = kinetic('div').preset(fade)
|
|
101
|
+
|
|
102
|
+
FadeDiv({ show: isOpen, children: 'Content' })
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Collapse
|
|
106
|
+
|
|
107
|
+
Height animation with `overflow: hidden`. Measures `scrollHeight` automatically.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const Accordion = kinetic('div').collapse()
|
|
111
|
+
const FancyAccordion = kinetic('section').collapse({
|
|
112
|
+
transition: 'height 400ms cubic-bezier(0.4, 0, 0.2, 1)'
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
Accordion({ show: isExpanded, children: 'Expandable content' })
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### Stagger
|
|
119
|
+
|
|
120
|
+
Staggered entrance/exit for child elements.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
const StaggerList = kinetic('ul').preset(slideUp).stagger({ interval: 75 })
|
|
124
|
+
|
|
125
|
+
StaggerList({ show: isVisible, children: [
|
|
126
|
+
h('li', { key: '1' }, 'Item 1'),
|
|
127
|
+
h('li', { key: '2' }, 'Item 2'),
|
|
128
|
+
h('li', { key: '3' }, 'Item 3'),
|
|
129
|
+
]})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Group
|
|
133
|
+
|
|
134
|
+
Key-based enter/exit — adding a child triggers enter animation, removing triggers leave + unmount. No `show` prop.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const AnimatedList = kinetic('ul').preset(fade).group()
|
|
138
|
+
|
|
139
|
+
AnimatedList({ children: items.map(item =>
|
|
140
|
+
h('li', { key: item.id }, item.text)
|
|
141
|
+
)})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Inline Configuration
|
|
145
|
+
|
|
146
|
+
Build animations without presets:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
const SlidePanel = kinetic('aside')
|
|
150
|
+
.enter({ opacity: 0, transform: 'translateX(-100%)' })
|
|
151
|
+
.enterTo({ opacity: 1, transform: 'translateX(0)' })
|
|
152
|
+
.enterTransition('all 300ms ease-out')
|
|
153
|
+
.leave({ opacity: 1, transform: 'translateX(0)' })
|
|
154
|
+
.leaveTo({ opacity: 0, transform: 'translateX(-100%)' })
|
|
155
|
+
.leaveTransition('all 200ms ease-in')
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Class-Based Transitions
|
|
159
|
+
|
|
160
|
+
Works with Tailwind CSS, CSS modules, or any class-based approach:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const TailwindFade = kinetic('div')
|
|
164
|
+
.enterClass({ active: 'transition-opacity duration-300', from: 'opacity-0', to: 'opacity-100' })
|
|
165
|
+
.leaveClass({ active: 'transition-opacity duration-200', from: 'opacity-100', to: 'opacity-0' })
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Lifecycle Callbacks
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
FadeDiv({
|
|
172
|
+
show: isOpen,
|
|
173
|
+
onEnter: () => console.log('entering'),
|
|
174
|
+
onAfterEnter: () => console.log('entered'),
|
|
175
|
+
onLeave: () => console.log('leaving'),
|
|
176
|
+
onAfterLeave: () => console.log('left'),
|
|
177
|
+
children: 'Content',
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Accessibility
|
|
182
|
+
|
|
183
|
+
Kinetic automatically detects `prefers-reduced-motion: reduce`. When enabled, animations are skipped instantly — callbacks still fire, but no visual animation occurs. No configuration needed.
|
|
184
|
+
|
|
185
|
+
## Built-in Presets
|
|
186
|
+
|
|
187
|
+
Six presets are included in the core package:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { fade, scaleIn, slideUp, slideDown, slideLeft, slideRight } from '@pyreon/kinetic'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
For 122 presets, factories, and composition utilities, install `@pyreon/kinetic-presets`.
|
|
194
|
+
|
|
195
|
+
## Composition with Rocketstyle
|
|
196
|
+
|
|
197
|
+
Kinetic and rocketstyle compose naturally:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import rocketstyle from '@pyreon/rocketstyle'
|
|
201
|
+
|
|
202
|
+
const Button = rocketstyle()({ component: 'button', name: 'Button' })
|
|
203
|
+
.theme({ primaryColor: 'blue' })
|
|
204
|
+
|
|
205
|
+
const AnimatedButton = kinetic(Button).preset(fade)
|
|
206
|
+
|
|
207
|
+
// Has both rocketstyle props AND kinetic props
|
|
208
|
+
AnimatedButton({ show: isVisible, primary: true, size: 'large', children: 'Click me' })
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Peer Dependencies
|
|
212
|
+
|
|
213
|
+
| Package | Version |
|
|
214
|
+
| ------- | ------- |
|
|
215
|
+
| @pyreon/core | >= 0.0.1 |
|
|
216
|
+
| @pyreon/reactivity | >= 0.0.1 |
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { ComponentFn, Ref, VNodeChild } from "@pyreon/core";
|
|
2
|
+
import { Signal } from "@pyreon/reactivity";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
type CSSProperties = Record<string, string | number | undefined>;
|
|
6
|
+
/** Internal lifecycle stages of a transition. */
|
|
7
|
+
type TransitionStage = "hidden" | "entering" | "entered" | "leaving";
|
|
8
|
+
/** Class-based transition definition. */
|
|
9
|
+
type ClassTransitionProps = {
|
|
10
|
+
/** Classes applied during the entire enter phase */enter?: string | undefined; /** Classes applied on first frame of enter, removed on next frame */
|
|
11
|
+
enterFrom?: string | undefined; /** Classes applied on second frame of enter, kept until complete */
|
|
12
|
+
enterTo?: string | undefined; /** Classes applied during the entire leave phase */
|
|
13
|
+
leave?: string | undefined; /** Classes applied on first frame of leave */
|
|
14
|
+
leaveFrom?: string | undefined; /** Classes applied on second frame of leave, kept until complete */
|
|
15
|
+
leaveTo?: string | undefined;
|
|
16
|
+
};
|
|
17
|
+
/** Style-object transition definition (zero-CSS option). */
|
|
18
|
+
type StyleTransitionProps = {
|
|
19
|
+
/** Inline styles for the start of enter */enterStyle?: CSSProperties | undefined; /** Inline styles for the end of enter */
|
|
20
|
+
enterToStyle?: CSSProperties | undefined; /** CSS transition shorthand applied during enter */
|
|
21
|
+
enterTransition?: string | undefined; /** Inline styles for the start of leave */
|
|
22
|
+
leaveStyle?: CSSProperties | undefined; /** Inline styles for the end of leave */
|
|
23
|
+
leaveToStyle?: CSSProperties | undefined; /** CSS transition shorthand applied during leave */
|
|
24
|
+
leaveTransition?: string | undefined;
|
|
25
|
+
};
|
|
26
|
+
/** Lifecycle callbacks. */
|
|
27
|
+
type TransitionCallbacks = {
|
|
28
|
+
/** Called immediately when entering begins */onEnter?: (() => void) | undefined; /** Called when enter animation completes */
|
|
29
|
+
onAfterEnter?: (() => void) | undefined; /** Called immediately when leaving begins */
|
|
30
|
+
onLeave?: (() => void) | undefined; /** Called when leave animation completes */
|
|
31
|
+
onAfterLeave?: (() => void) | undefined;
|
|
32
|
+
};
|
|
33
|
+
type TransitionStateResult = {
|
|
34
|
+
/** Current lifecycle stage (signal) */stage: Signal<TransitionStage>; /** Ref callback to attach to the transitioning element */
|
|
35
|
+
ref: Ref<HTMLElement> | ((node: HTMLElement | null) => void); /** Reactive accessor: whether the element should be rendered */
|
|
36
|
+
shouldMount: () => boolean; /** Call when the current animation finishes */
|
|
37
|
+
complete: () => void;
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/kinetic/types.d.ts
|
|
41
|
+
type KineticMode = "transition" | "collapse" | "stagger" | "group";
|
|
42
|
+
type ClassConfig = {
|
|
43
|
+
active?: string | undefined;
|
|
44
|
+
from?: string | undefined;
|
|
45
|
+
to?: string | undefined;
|
|
46
|
+
};
|
|
47
|
+
type TransitionConfigOpts = {
|
|
48
|
+
appear?: boolean | undefined;
|
|
49
|
+
unmount?: boolean | undefined;
|
|
50
|
+
timeout?: number | undefined;
|
|
51
|
+
};
|
|
52
|
+
type CollapseConfigOpts = {
|
|
53
|
+
appear?: boolean | undefined;
|
|
54
|
+
timeout?: number | undefined;
|
|
55
|
+
transition?: string | undefined;
|
|
56
|
+
};
|
|
57
|
+
type StaggerConfigOpts = {
|
|
58
|
+
appear?: boolean | undefined;
|
|
59
|
+
timeout?: number | undefined;
|
|
60
|
+
interval?: number | undefined;
|
|
61
|
+
reverseLeave?: boolean | undefined;
|
|
62
|
+
};
|
|
63
|
+
type GroupConfigOpts = {
|
|
64
|
+
appear?: boolean | undefined;
|
|
65
|
+
timeout?: number | undefined;
|
|
66
|
+
};
|
|
67
|
+
type KineticTransitionProps<_Tag extends string> = Record<string, unknown> & {
|
|
68
|
+
show: () => boolean;
|
|
69
|
+
appear?: boolean | undefined;
|
|
70
|
+
unmount?: boolean | undefined;
|
|
71
|
+
timeout?: number | undefined;
|
|
72
|
+
children?: VNodeChild | undefined;
|
|
73
|
+
} & Partial<TransitionCallbacks>;
|
|
74
|
+
type KineticCollapseProps<_Tag extends string> = Record<string, unknown> & {
|
|
75
|
+
show: () => boolean;
|
|
76
|
+
appear?: boolean | undefined;
|
|
77
|
+
timeout?: number | undefined;
|
|
78
|
+
transition?: string | undefined;
|
|
79
|
+
children?: VNodeChild | undefined;
|
|
80
|
+
} & Partial<TransitionCallbacks>;
|
|
81
|
+
type KineticStaggerProps<_Tag extends string> = Record<string, unknown> & {
|
|
82
|
+
show: () => boolean;
|
|
83
|
+
appear?: boolean | undefined;
|
|
84
|
+
timeout?: number | undefined;
|
|
85
|
+
interval?: number | undefined;
|
|
86
|
+
reverseLeave?: boolean | undefined;
|
|
87
|
+
children: VNodeChild;
|
|
88
|
+
} & Partial<TransitionCallbacks>;
|
|
89
|
+
type KineticGroupProps<_Tag extends string> = Record<string, unknown> & {
|
|
90
|
+
appear?: boolean | undefined;
|
|
91
|
+
timeout?: number | undefined;
|
|
92
|
+
children: VNodeChild;
|
|
93
|
+
} & Partial<TransitionCallbacks>;
|
|
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
|
+
type KineticChain<Tag extends string, Mode extends KineticMode> = {
|
|
97
|
+
displayName: string;
|
|
98
|
+
preset: (preset: StyleTransitionProps & ClassTransitionProps) => KineticComponent<Tag, Mode>;
|
|
99
|
+
enter: (styles: CSSProperties) => KineticComponent<Tag, Mode>;
|
|
100
|
+
enterTo: (styles: CSSProperties) => KineticComponent<Tag, Mode>;
|
|
101
|
+
enterTransition: (value: string) => KineticComponent<Tag, Mode>;
|
|
102
|
+
leave: (styles: CSSProperties) => KineticComponent<Tag, Mode>;
|
|
103
|
+
leaveTo: (styles: CSSProperties) => KineticComponent<Tag, Mode>;
|
|
104
|
+
leaveTransition: (value: string) => KineticComponent<Tag, Mode>;
|
|
105
|
+
enterClass: (opts: ClassConfig) => KineticComponent<Tag, Mode>;
|
|
106
|
+
leaveClass: (opts: ClassConfig) => KineticComponent<Tag, Mode>;
|
|
107
|
+
config: (opts: ConfigOpts<Mode>) => KineticComponent<Tag, Mode>;
|
|
108
|
+
on: (callbacks: Partial<TransitionCallbacks>) => KineticComponent<Tag, Mode>;
|
|
109
|
+
collapse: (opts?: CollapseConfigOpts) => KineticComponent<Tag, "collapse">;
|
|
110
|
+
stagger: (opts?: {
|
|
111
|
+
interval?: number | undefined;
|
|
112
|
+
reverseLeave?: boolean | undefined;
|
|
113
|
+
}) => KineticComponent<Tag, "stagger">;
|
|
114
|
+
group: () => KineticComponent<Tag, "group">;
|
|
115
|
+
};
|
|
116
|
+
type KineticComponent<Tag extends string, Mode extends KineticMode = "transition"> = ComponentFn<KineticComponentProps<Tag, Mode>> & KineticChain<Tag, Mode>;
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/kinetic.d.ts
|
|
119
|
+
/**
|
|
120
|
+
* Creates a reusable animated component via immutable chaining.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```tsx
|
|
124
|
+
* // Transition (default)
|
|
125
|
+
* const FadeDiv = kinetic('div').preset(fade)
|
|
126
|
+
*
|
|
127
|
+
* // Collapse
|
|
128
|
+
* const Accordion = kinetic('div').collapse()
|
|
129
|
+
*
|
|
130
|
+
* // Stagger
|
|
131
|
+
* const StaggerList = kinetic('ul').preset(slideUp).stagger({ interval: 50 })
|
|
132
|
+
*
|
|
133
|
+
* // Group (key-based enter/exit)
|
|
134
|
+
* const AnimatedList = kinetic('ul').preset(fade).group()
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
declare const kinetic: <Tag extends string>(tag: Tag) => KineticComponent<Tag, "transition">;
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/presets.d.ts
|
|
140
|
+
type Preset = StyleTransitionProps & ClassTransitionProps;
|
|
141
|
+
declare const fade: Preset;
|
|
142
|
+
declare const scaleIn: Preset;
|
|
143
|
+
declare const slideUp: Preset;
|
|
144
|
+
declare const slideDown: Preset;
|
|
145
|
+
declare const slideLeft: Preset;
|
|
146
|
+
declare const slideRight: Preset;
|
|
147
|
+
declare const presets: {
|
|
148
|
+
readonly fade: Preset;
|
|
149
|
+
readonly scaleIn: Preset;
|
|
150
|
+
readonly slideUp: Preset;
|
|
151
|
+
readonly slideDown: Preset;
|
|
152
|
+
readonly slideLeft: Preset;
|
|
153
|
+
readonly slideRight: Preset;
|
|
154
|
+
};
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/useAnimationEnd.d.ts
|
|
157
|
+
type UseAnimationEnd = (options: {
|
|
158
|
+
ref: Ref<HTMLElement>;
|
|
159
|
+
onEnd: () => void;
|
|
160
|
+
active: () => boolean;
|
|
161
|
+
timeout?: number | undefined;
|
|
162
|
+
}) => void;
|
|
163
|
+
declare const useAnimationEnd: UseAnimationEnd;
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/useTransitionState.d.ts
|
|
166
|
+
type UseTransitionState = (options: {
|
|
167
|
+
show: () => boolean;
|
|
168
|
+
appear?: boolean | undefined;
|
|
169
|
+
}) => TransitionStateResult;
|
|
170
|
+
declare const useTransitionState: UseTransitionState;
|
|
171
|
+
//#endregion
|
|
172
|
+
export { type ClassTransitionProps, type KineticComponent, type Preset, type StyleTransitionProps, type TransitionCallbacks, type TransitionStage, type TransitionStateResult, type UseAnimationEnd, type UseTransitionState, fade, kinetic, presets, scaleIn, slideDown, slideLeft, slideRight, slideUp, useAnimationEnd, useTransitionState };
|
|
173
|
+
//# sourceMappingURL=index2.d.ts.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,903 @@
|
|
|
1
|
+
import { Show, createRef, h, onMount, onUnmount } from "@pyreon/core";
|
|
2
|
+
import { runUntracked, signal, watch } from "@pyreon/reactivity";
|
|
3
|
+
import { jsx } from "@pyreon/core/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/useAnimationEnd.ts
|
|
6
|
+
const DEFAULT_TIMEOUT = 5e3;
|
|
7
|
+
const useAnimationEnd = ({ ref, onEnd, active, timeout = DEFAULT_TIMEOUT }) => {
|
|
8
|
+
let called = false;
|
|
9
|
+
watch(active, (isActive) => {
|
|
10
|
+
if (!isActive) {
|
|
11
|
+
called = false;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const el = ref.current;
|
|
15
|
+
if (!el) return;
|
|
16
|
+
called = false;
|
|
17
|
+
const done = () => {
|
|
18
|
+
if (called) return;
|
|
19
|
+
called = true;
|
|
20
|
+
el.removeEventListener("transitionend", handleEnd);
|
|
21
|
+
el.removeEventListener("animationend", handleEnd);
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
onEnd();
|
|
24
|
+
};
|
|
25
|
+
const handleEnd = (e) => {
|
|
26
|
+
if (e.target !== el) return;
|
|
27
|
+
done();
|
|
28
|
+
};
|
|
29
|
+
el.addEventListener("transitionend", handleEnd);
|
|
30
|
+
el.addEventListener("animationend", handleEnd);
|
|
31
|
+
const timer = setTimeout(done, timeout);
|
|
32
|
+
return () => {
|
|
33
|
+
el.removeEventListener("transitionend", handleEnd);
|
|
34
|
+
el.removeEventListener("animationend", handleEnd);
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
};
|
|
37
|
+
}, { immediate: true });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/useReducedMotion.ts
|
|
42
|
+
/**
|
|
43
|
+
* Inline reduced-motion check for kinetic package.
|
|
44
|
+
* Avoids depending on @pyreon/hooks for a single media query.
|
|
45
|
+
*/
|
|
46
|
+
function useReducedMotion() {
|
|
47
|
+
const matches = signal(false);
|
|
48
|
+
let mql;
|
|
49
|
+
const onChange = (e) => {
|
|
50
|
+
matches.set(e.matches);
|
|
51
|
+
};
|
|
52
|
+
onMount(() => {
|
|
53
|
+
mql = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
54
|
+
matches.set(mql.matches);
|
|
55
|
+
mql.addEventListener("change", onChange);
|
|
56
|
+
});
|
|
57
|
+
onUnmount(() => {
|
|
58
|
+
mql?.removeEventListener("change", onChange);
|
|
59
|
+
});
|
|
60
|
+
return matches;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/kinetic/CollapseRenderer.tsx
|
|
65
|
+
/**
|
|
66
|
+
* Renders a height-animated collapse. The config.tag becomes the outer
|
|
67
|
+
* wrapper (overflow:hidden + animated height). An inner div measures
|
|
68
|
+
* scrollHeight for the target value.
|
|
69
|
+
*/
|
|
70
|
+
const CollapseRenderer = ({ config, htmlProps, show, appear, timeout, transition, callbacks, children }) => {
|
|
71
|
+
const reducedMotion = useReducedMotion();
|
|
72
|
+
let wrapperRef = createRef();
|
|
73
|
+
const contentRef = createRef();
|
|
74
|
+
const effectiveAppear = appear ?? config.appear ?? false;
|
|
75
|
+
const effectiveTimeout = timeout ?? config.timeout ?? 5e3;
|
|
76
|
+
const effectiveTransition = transition ?? config.transition ?? "height 300ms ease";
|
|
77
|
+
const initialShow = show();
|
|
78
|
+
const needsAppear = effectiveAppear && initialShow;
|
|
79
|
+
const stage = signal(initialShow ? "entered" : "hidden");
|
|
80
|
+
let isInitialMount = true;
|
|
81
|
+
let appearTriggered = false;
|
|
82
|
+
if (needsAppear) {
|
|
83
|
+
const orig = wrapperRef;
|
|
84
|
+
const proxy = { current: null };
|
|
85
|
+
Object.defineProperty(proxy, "current", {
|
|
86
|
+
get() {
|
|
87
|
+
return orig.current;
|
|
88
|
+
},
|
|
89
|
+
set(node) {
|
|
90
|
+
orig.current = node;
|
|
91
|
+
if (node && !appearTriggered) {
|
|
92
|
+
appearTriggered = true;
|
|
93
|
+
queueMicrotask(() => stage.set("entering"));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
wrapperRef = proxy;
|
|
98
|
+
}
|
|
99
|
+
watch(show, (showVal) => {
|
|
100
|
+
if (isInitialMount) {
|
|
101
|
+
isInitialMount = false;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const currentStage = runUntracked(() => stage());
|
|
105
|
+
if (showVal && (currentStage === "hidden" || currentStage === "leaving")) stage.set("entering");
|
|
106
|
+
else if (!showVal && (currentStage === "entered" || currentStage === "entering")) stage.set("leaving");
|
|
107
|
+
}, { immediate: true });
|
|
108
|
+
watch(() => stage(), (currentStage) => {
|
|
109
|
+
const wrapper = wrapperRef.current;
|
|
110
|
+
const content = contentRef.current;
|
|
111
|
+
if (!wrapper || !content) return;
|
|
112
|
+
if (reducedMotion()) {
|
|
113
|
+
if (currentStage === "entering") {
|
|
114
|
+
callbacks.onEnter?.();
|
|
115
|
+
wrapper.style.height = "auto";
|
|
116
|
+
wrapper.style.overflow = "";
|
|
117
|
+
callbacks.onAfterEnter?.();
|
|
118
|
+
stage.set("entered");
|
|
119
|
+
} else if (currentStage === "leaving") {
|
|
120
|
+
callbacks.onLeave?.();
|
|
121
|
+
wrapper.style.height = "0px";
|
|
122
|
+
wrapper.style.overflow = "hidden";
|
|
123
|
+
callbacks.onAfterLeave?.();
|
|
124
|
+
stage.set("hidden");
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (currentStage === "entering") {
|
|
129
|
+
callbacks.onEnter?.();
|
|
130
|
+
const height = content.scrollHeight;
|
|
131
|
+
wrapper.style.transition = "none";
|
|
132
|
+
wrapper.style.height = "0px";
|
|
133
|
+
wrapper.style.overflow = "hidden";
|
|
134
|
+
wrapper.offsetHeight;
|
|
135
|
+
wrapper.style.transition = effectiveTransition;
|
|
136
|
+
wrapper.style.height = `${height}px`;
|
|
137
|
+
}
|
|
138
|
+
if (currentStage === "leaving") {
|
|
139
|
+
callbacks.onLeave?.();
|
|
140
|
+
const height = content.scrollHeight;
|
|
141
|
+
wrapper.style.transition = "none";
|
|
142
|
+
wrapper.style.height = `${height}px`;
|
|
143
|
+
wrapper.style.overflow = "hidden";
|
|
144
|
+
wrapper.offsetHeight;
|
|
145
|
+
wrapper.style.transition = effectiveTransition;
|
|
146
|
+
wrapper.style.height = "0px";
|
|
147
|
+
}
|
|
148
|
+
}, { immediate: true });
|
|
149
|
+
useAnimationEnd({
|
|
150
|
+
ref: wrapperRef,
|
|
151
|
+
active: () => (stage() === "entering" || stage() === "leaving") && !reducedMotion(),
|
|
152
|
+
timeout: effectiveTimeout,
|
|
153
|
+
onEnd: () => {
|
|
154
|
+
const wrapper = wrapperRef.current;
|
|
155
|
+
if (stage() === "entering") {
|
|
156
|
+
if (wrapper) {
|
|
157
|
+
wrapper.style.height = "auto";
|
|
158
|
+
wrapper.style.overflow = "";
|
|
159
|
+
wrapper.style.transition = "";
|
|
160
|
+
}
|
|
161
|
+
callbacks.onAfterEnter?.();
|
|
162
|
+
stage.set("entered");
|
|
163
|
+
} else if (stage() === "leaving") {
|
|
164
|
+
callbacks.onAfterLeave?.();
|
|
165
|
+
stage.set("hidden");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
const shouldRender = () => stage() !== "hidden";
|
|
170
|
+
const wrapperStyle = {
|
|
171
|
+
...htmlProps.style ?? {},
|
|
172
|
+
...stage() !== "entered" ? { overflow: "hidden" } : {},
|
|
173
|
+
...stage() === "hidden" ? { height: "0px" } : stage() === "entered" ? { height: "auto" } : {}
|
|
174
|
+
};
|
|
175
|
+
return h(config.tag, {
|
|
176
|
+
ref: wrapperRef,
|
|
177
|
+
...htmlProps,
|
|
178
|
+
style: wrapperStyle
|
|
179
|
+
}, /* @__PURE__ */ jsx(Show, {
|
|
180
|
+
when: shouldRender,
|
|
181
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
182
|
+
ref: contentRef,
|
|
183
|
+
children
|
|
184
|
+
})
|
|
185
|
+
}));
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/useTransitionState.ts
|
|
190
|
+
const useTransitionState = ({ show, appear = false }) => {
|
|
191
|
+
const initialShow = show();
|
|
192
|
+
const needsAppear = appear && initialShow;
|
|
193
|
+
const stage = signal(initialShow ? "entered" : "hidden");
|
|
194
|
+
const elementRef = createRef();
|
|
195
|
+
let isInitialMount = true;
|
|
196
|
+
let appearTriggered = false;
|
|
197
|
+
const refCallback = (node) => {
|
|
198
|
+
elementRef.current = node;
|
|
199
|
+
if (node && needsAppear && !appearTriggered) {
|
|
200
|
+
appearTriggered = true;
|
|
201
|
+
stage.set("entering");
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
watch(show, (showVal) => {
|
|
205
|
+
if (isInitialMount) {
|
|
206
|
+
isInitialMount = false;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const currentStage = runUntracked(() => stage());
|
|
210
|
+
if (showVal && (currentStage === "hidden" || currentStage === "leaving")) stage.set("entering");
|
|
211
|
+
else if (!showVal && (currentStage === "entered" || currentStage === "entering")) stage.set("leaving");
|
|
212
|
+
}, { immediate: true });
|
|
213
|
+
const complete = () => {
|
|
214
|
+
const current = stage();
|
|
215
|
+
if (current === "entering") stage.set("entered");
|
|
216
|
+
if (current === "leaving") stage.set("hidden");
|
|
217
|
+
};
|
|
218
|
+
return {
|
|
219
|
+
stage,
|
|
220
|
+
ref: refCallback,
|
|
221
|
+
shouldMount: () => stage() !== "hidden",
|
|
222
|
+
complete
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/utils.ts
|
|
228
|
+
const splitCache = /* @__PURE__ */ new Map();
|
|
229
|
+
const splitClasses = (classes) => {
|
|
230
|
+
let cached = splitCache.get(classes);
|
|
231
|
+
if (!cached) {
|
|
232
|
+
cached = classes.split(/\s+/).filter(Boolean);
|
|
233
|
+
splitCache.set(classes, cached);
|
|
234
|
+
}
|
|
235
|
+
return cached;
|
|
236
|
+
};
|
|
237
|
+
/** Adds space-separated CSS classes to an element. */
|
|
238
|
+
const addClasses = (el, classes) => {
|
|
239
|
+
if (!classes) return;
|
|
240
|
+
const list = splitClasses(classes);
|
|
241
|
+
if (list.length > 0) el.classList.add(...list);
|
|
242
|
+
};
|
|
243
|
+
/** Removes space-separated CSS classes from an element. */
|
|
244
|
+
const removeClasses = (el, classes) => {
|
|
245
|
+
if (!classes) return;
|
|
246
|
+
const list = splitClasses(classes);
|
|
247
|
+
if (list.length > 0) el.classList.remove(...list);
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* Executes callback after two animation frames (double-rAF).
|
|
251
|
+
* Ensures the browser paints the current state before applying changes,
|
|
252
|
+
* which is required for CSS transitions to trigger.
|
|
253
|
+
*/
|
|
254
|
+
const nextFrame = (callback) => requestAnimationFrame(() => {
|
|
255
|
+
requestAnimationFrame(callback);
|
|
256
|
+
});
|
|
257
|
+
/** Merges two CSSProperties objects, with `b` taking precedence. */
|
|
258
|
+
const mergeStyles = (a, b) => {
|
|
259
|
+
if (!a && !b) return void 0;
|
|
260
|
+
if (!a) return b;
|
|
261
|
+
if (!b) return a;
|
|
262
|
+
return {
|
|
263
|
+
...a,
|
|
264
|
+
...b
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
/** Merges multiple refs (callback or object) into a single callback ref. */
|
|
268
|
+
const mergeRefs = (...refs) => {
|
|
269
|
+
return (node) => {
|
|
270
|
+
for (const ref of refs) {
|
|
271
|
+
if (!ref) continue;
|
|
272
|
+
if (typeof ref === "function") ref(node);
|
|
273
|
+
else ref.current = node;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
/** Clones a VNode with merged props. */
|
|
278
|
+
const cloneVNode = (vnode, extraProps) => ({
|
|
279
|
+
...vnode,
|
|
280
|
+
props: {
|
|
281
|
+
...vnode.props,
|
|
282
|
+
...extraProps
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/kinetic/TransitionItem.tsx
|
|
288
|
+
const applyEnter$1 = (el, config) => {
|
|
289
|
+
addClasses(el, config.enter);
|
|
290
|
+
addClasses(el, config.enterFrom);
|
|
291
|
+
if (config.enterStyle) Object.assign(el.style, config.enterStyle);
|
|
292
|
+
if (config.enterTransition) el.style.transition = config.enterTransition;
|
|
293
|
+
return nextFrame(() => {
|
|
294
|
+
removeClasses(el, config.enterFrom);
|
|
295
|
+
addClasses(el, config.enterTo);
|
|
296
|
+
if (config.enterToStyle) Object.assign(el.style, config.enterToStyle);
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
const applyLeave$1 = (el, config) => {
|
|
300
|
+
removeClasses(el, config.enter);
|
|
301
|
+
removeClasses(el, config.enterTo);
|
|
302
|
+
addClasses(el, config.leave);
|
|
303
|
+
addClasses(el, config.leaveFrom);
|
|
304
|
+
if (config.leaveStyle) Object.assign(el.style, config.leaveStyle);
|
|
305
|
+
if (config.leaveTransition) el.style.transition = config.leaveTransition;
|
|
306
|
+
return nextFrame(() => {
|
|
307
|
+
removeClasses(el, config.leaveFrom);
|
|
308
|
+
addClasses(el, config.leaveTo);
|
|
309
|
+
if (config.leaveToStyle) Object.assign(el.style, config.leaveToStyle);
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
const applyReducedMotion$1 = (stage, callbacks, complete) => {
|
|
313
|
+
if (stage === "entering") {
|
|
314
|
+
callbacks.onEnter?.();
|
|
315
|
+
callbacks.onAfterEnter?.();
|
|
316
|
+
complete();
|
|
317
|
+
} else if (stage === "leaving") {
|
|
318
|
+
callbacks.onLeave?.();
|
|
319
|
+
callbacks.onAfterLeave?.();
|
|
320
|
+
complete();
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* Internal per-child transition component. Used by StaggerRenderer and
|
|
325
|
+
* GroupRenderer to give each child its own animation state.
|
|
326
|
+
*
|
|
327
|
+
* Uses cloneVNode to inject ref onto the child — the child must accept ref.
|
|
328
|
+
*/
|
|
329
|
+
const TransitionItem = ({ show, appear = false, unmount = true, timeout = 5e3, enter, enterFrom, enterTo, leave, leaveFrom, leaveTo, enterStyle, enterToStyle, enterTransition, leaveStyle, leaveToStyle, leaveTransition, onEnter, onAfterEnter, onLeave, onAfterLeave, children }) => {
|
|
330
|
+
const reducedMotion = useReducedMotion();
|
|
331
|
+
const { stage, ref: stateRef, shouldMount, complete } = useTransitionState({
|
|
332
|
+
show,
|
|
333
|
+
appear
|
|
334
|
+
});
|
|
335
|
+
const elementRef = createRef();
|
|
336
|
+
const mergedRef = mergeRefs(elementRef, stateRef, children.props?.ref);
|
|
337
|
+
const callbacks = {
|
|
338
|
+
onEnter,
|
|
339
|
+
onAfterEnter,
|
|
340
|
+
onLeave,
|
|
341
|
+
onAfterLeave
|
|
342
|
+
};
|
|
343
|
+
const transitionConfig = {
|
|
344
|
+
enter,
|
|
345
|
+
enterFrom,
|
|
346
|
+
enterTo,
|
|
347
|
+
leave,
|
|
348
|
+
leaveFrom,
|
|
349
|
+
leaveTo,
|
|
350
|
+
enterStyle,
|
|
351
|
+
enterToStyle,
|
|
352
|
+
enterTransition,
|
|
353
|
+
leaveStyle,
|
|
354
|
+
leaveToStyle,
|
|
355
|
+
leaveTransition
|
|
356
|
+
};
|
|
357
|
+
useAnimationEnd({
|
|
358
|
+
ref: elementRef,
|
|
359
|
+
active: () => (stage() === "entering" || stage() === "leaving") && !reducedMotion(),
|
|
360
|
+
timeout,
|
|
361
|
+
onEnd: () => {
|
|
362
|
+
if (stage() === "entering") callbacks.onAfterEnter?.();
|
|
363
|
+
else if (stage() === "leaving") callbacks.onAfterLeave?.();
|
|
364
|
+
complete();
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
watch(() => stage(), (currentStage) => {
|
|
368
|
+
const el = elementRef.current;
|
|
369
|
+
if (!el) return;
|
|
370
|
+
if (reducedMotion()) {
|
|
371
|
+
applyReducedMotion$1(currentStage, callbacks, complete);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (currentStage === "entering") {
|
|
375
|
+
callbacks.onEnter?.();
|
|
376
|
+
const frameId = applyEnter$1(el, transitionConfig);
|
|
377
|
+
return () => cancelAnimationFrame(frameId);
|
|
378
|
+
}
|
|
379
|
+
if (currentStage === "leaving") {
|
|
380
|
+
callbacks.onLeave?.();
|
|
381
|
+
const frameId = applyLeave$1(el, transitionConfig);
|
|
382
|
+
return () => cancelAnimationFrame(frameId);
|
|
383
|
+
}
|
|
384
|
+
if (currentStage === "entered") {
|
|
385
|
+
removeClasses(el, enter);
|
|
386
|
+
el.style.transition = "";
|
|
387
|
+
}
|
|
388
|
+
}, { immediate: true });
|
|
389
|
+
return /* @__PURE__ */ jsx(Show, {
|
|
390
|
+
when: shouldMount,
|
|
391
|
+
fallback: unmount ? null : cloneVNode(children, {
|
|
392
|
+
ref: mergedRef,
|
|
393
|
+
style: mergeStyles(children.props?.style, { display: "none" })
|
|
394
|
+
}),
|
|
395
|
+
children: cloneVNode(children, { ref: mergedRef })
|
|
396
|
+
});
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/kinetic/GroupRenderer.tsx
|
|
401
|
+
const isVNode$1 = (child) => child != null && typeof child === "object" && "type" in child;
|
|
402
|
+
const getKeyedChildren = (children) => {
|
|
403
|
+
const result = [];
|
|
404
|
+
for (const child of children) if (isVNode$1(child)) {
|
|
405
|
+
const key = child.key;
|
|
406
|
+
if (key != null) result.push({
|
|
407
|
+
key,
|
|
408
|
+
element: child
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return result;
|
|
412
|
+
};
|
|
413
|
+
/**
|
|
414
|
+
* Renders children with key-based enter/exit animation (no `show` prop).
|
|
415
|
+
* Children that appear (new key) animate in. Children that disappear
|
|
416
|
+
* (removed key) stay in DOM during leave animation, then unmount.
|
|
417
|
+
* config.tag wraps all children as a container element.
|
|
418
|
+
*/
|
|
419
|
+
const GroupRenderer = ({ config, htmlProps, appear, timeout, callbacks, children }) => {
|
|
420
|
+
const effectiveAppear = appear ?? config.appear ?? false;
|
|
421
|
+
const effectiveTimeout = timeout ?? config.timeout ?? 5e3;
|
|
422
|
+
const prevMap = /* @__PURE__ */ new Map();
|
|
423
|
+
const leavingMap = /* @__PURE__ */ new Map();
|
|
424
|
+
let initialKeys = null;
|
|
425
|
+
const forceUpdateSignal = signal(0);
|
|
426
|
+
const currentKeyed = getKeyedChildren(children);
|
|
427
|
+
const currentMap = /* @__PURE__ */ new Map();
|
|
428
|
+
for (const { key, element } of currentKeyed) currentMap.set(key, element);
|
|
429
|
+
if (initialKeys === null) initialKeys = new Set(currentMap.keys());
|
|
430
|
+
for (const [key, child] of prevMap) if (!currentMap.has(key)) leavingMap.set(key, child);
|
|
431
|
+
for (const key of currentMap.keys()) leavingMap.delete(key);
|
|
432
|
+
prevMap.clear();
|
|
433
|
+
for (const [key, element] of currentMap) prevMap.set(key, element);
|
|
434
|
+
const handleAfterLeave = (key) => {
|
|
435
|
+
leavingMap.delete(key);
|
|
436
|
+
callbacks.onAfterLeave?.();
|
|
437
|
+
forceUpdateSignal.update((c) => c + 1);
|
|
438
|
+
};
|
|
439
|
+
const allEntries = [...currentKeyed];
|
|
440
|
+
for (const [key, element] of leavingMap) allEntries.push({
|
|
441
|
+
key,
|
|
442
|
+
element
|
|
443
|
+
});
|
|
444
|
+
const groupedChildren = allEntries.map(({ key, element }) => {
|
|
445
|
+
const isInitial = initialKeys?.has(key) ?? false;
|
|
446
|
+
const isShowing = currentMap.has(key);
|
|
447
|
+
return /* @__PURE__ */ jsx(TransitionItem, {
|
|
448
|
+
show: () => isShowing,
|
|
449
|
+
appear: isInitial ? effectiveAppear : true,
|
|
450
|
+
timeout: effectiveTimeout,
|
|
451
|
+
enterStyle: config.enterStyle,
|
|
452
|
+
enterToStyle: config.enterToStyle,
|
|
453
|
+
enterTransition: config.enterTransition,
|
|
454
|
+
leaveStyle: config.leaveStyle,
|
|
455
|
+
leaveToStyle: config.leaveToStyle,
|
|
456
|
+
leaveTransition: config.leaveTransition,
|
|
457
|
+
enter: config.enter,
|
|
458
|
+
enterFrom: config.enterFrom,
|
|
459
|
+
enterTo: config.enterTo,
|
|
460
|
+
leave: config.leave,
|
|
461
|
+
leaveFrom: config.leaveFrom,
|
|
462
|
+
leaveTo: config.leaveTo,
|
|
463
|
+
onAfterLeave: () => handleAfterLeave(key),
|
|
464
|
+
children: element
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
return h(config.tag, { ...htmlProps }, ...groupedChildren);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/kinetic/StaggerRenderer.tsx
|
|
472
|
+
const isVNode = (child) => child != null && typeof child === "object" && "type" in child;
|
|
473
|
+
/**
|
|
474
|
+
* Renders children with staggered enter/exit animation.
|
|
475
|
+
* config.tag wraps the staggered children as a container element.
|
|
476
|
+
* Each child is individually animated via TransitionItem.
|
|
477
|
+
*/
|
|
478
|
+
const StaggerRenderer = ({ config, htmlProps, show, appear, timeout, interval, reverseLeave, callbacks, children }) => {
|
|
479
|
+
const effectiveAppear = appear ?? config.appear ?? false;
|
|
480
|
+
const effectiveTimeout = timeout ?? config.timeout ?? 5e3;
|
|
481
|
+
const effectiveInterval = interval ?? config.interval ?? 50;
|
|
482
|
+
const effectiveReverseLeave = reverseLeave ?? config.reverseLeave ?? false;
|
|
483
|
+
const childArray = (Array.isArray(children) ? children : [children]).filter(isVNode);
|
|
484
|
+
const count = childArray.length;
|
|
485
|
+
const staggeredChildren = childArray.map((child, index) => {
|
|
486
|
+
const staggerIndex = !show() && effectiveReverseLeave ? count - 1 - index : index;
|
|
487
|
+
const delay = staggerIndex * effectiveInterval;
|
|
488
|
+
return /* @__PURE__ */ jsx(TransitionItem, {
|
|
489
|
+
show,
|
|
490
|
+
appear: effectiveAppear,
|
|
491
|
+
timeout: effectiveTimeout + delay,
|
|
492
|
+
enterStyle: config.enterStyle,
|
|
493
|
+
enterToStyle: config.enterToStyle,
|
|
494
|
+
enterTransition: config.enterTransition,
|
|
495
|
+
leaveStyle: config.leaveStyle,
|
|
496
|
+
leaveToStyle: config.leaveToStyle,
|
|
497
|
+
leaveTransition: config.leaveTransition,
|
|
498
|
+
enter: config.enter,
|
|
499
|
+
enterFrom: config.enterFrom,
|
|
500
|
+
enterTo: config.enterTo,
|
|
501
|
+
leave: config.leave,
|
|
502
|
+
leaveFrom: config.leaveFrom,
|
|
503
|
+
leaveTo: config.leaveTo,
|
|
504
|
+
onAfterLeave: index === (effectiveReverseLeave ? 0 : count - 1) ? callbacks.onAfterLeave : void 0,
|
|
505
|
+
children: cloneVNode(child, { style: {
|
|
506
|
+
...child.props?.style,
|
|
507
|
+
"--stagger-index": staggerIndex,
|
|
508
|
+
"--stagger-interval": `${effectiveInterval}ms`,
|
|
509
|
+
transitionDelay: `${delay}ms`
|
|
510
|
+
} })
|
|
511
|
+
}, child.key ?? index);
|
|
512
|
+
});
|
|
513
|
+
return h(config.tag, { ...htmlProps }, ...staggeredChildren);
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/kinetic/TransitionRenderer.tsx
|
|
518
|
+
const applyEnter = (el, config) => {
|
|
519
|
+
addClasses(el, config.enter);
|
|
520
|
+
addClasses(el, config.enterFrom);
|
|
521
|
+
if (config.enterStyle) Object.assign(el.style, config.enterStyle);
|
|
522
|
+
if (config.enterTransition) el.style.transition = config.enterTransition;
|
|
523
|
+
return nextFrame(() => {
|
|
524
|
+
removeClasses(el, config.enterFrom);
|
|
525
|
+
addClasses(el, config.enterTo);
|
|
526
|
+
if (config.enterToStyle) Object.assign(el.style, config.enterToStyle);
|
|
527
|
+
});
|
|
528
|
+
};
|
|
529
|
+
const applyLeave = (el, config) => {
|
|
530
|
+
removeClasses(el, config.enter);
|
|
531
|
+
removeClasses(el, config.enterTo);
|
|
532
|
+
addClasses(el, config.leave);
|
|
533
|
+
addClasses(el, config.leaveFrom);
|
|
534
|
+
if (config.leaveStyle) Object.assign(el.style, config.leaveStyle);
|
|
535
|
+
if (config.leaveTransition) el.style.transition = config.leaveTransition;
|
|
536
|
+
return nextFrame(() => {
|
|
537
|
+
removeClasses(el, config.leaveFrom);
|
|
538
|
+
addClasses(el, config.leaveTo);
|
|
539
|
+
if (config.leaveToStyle) Object.assign(el.style, config.leaveToStyle);
|
|
540
|
+
});
|
|
541
|
+
};
|
|
542
|
+
const applyReducedMotion = (stage, cbs, complete) => {
|
|
543
|
+
if (stage === "entering") {
|
|
544
|
+
cbs.onEnter?.();
|
|
545
|
+
cbs.onAfterEnter?.();
|
|
546
|
+
complete();
|
|
547
|
+
} else if (stage === "leaving") {
|
|
548
|
+
cbs.onLeave?.();
|
|
549
|
+
cbs.onAfterLeave?.();
|
|
550
|
+
complete();
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
/**
|
|
554
|
+
* Renders a single element with CSS transition enter/exit animation.
|
|
555
|
+
* Uses h(config.tag) — no cloneElement needed.
|
|
556
|
+
*/
|
|
557
|
+
const TransitionRenderer = ({ config, htmlProps, show, appear, unmount, timeout, callbacks, children }) => {
|
|
558
|
+
const reducedMotion = useReducedMotion();
|
|
559
|
+
const { stage, ref: stateRef, shouldMount, complete } = useTransitionState({
|
|
560
|
+
show,
|
|
561
|
+
appear: appear ?? config.appear ?? false
|
|
562
|
+
});
|
|
563
|
+
const elementRef = createRef();
|
|
564
|
+
const mergedRef = mergeRefs(elementRef, stateRef);
|
|
565
|
+
const effectiveUnmount = unmount ?? config.unmount ?? true;
|
|
566
|
+
useAnimationEnd({
|
|
567
|
+
ref: elementRef,
|
|
568
|
+
active: () => (stage() === "entering" || stage() === "leaving") && !reducedMotion(),
|
|
569
|
+
timeout: timeout ?? config.timeout ?? 5e3,
|
|
570
|
+
onEnd: () => {
|
|
571
|
+
if (stage() === "entering") callbacks.onAfterEnter?.();
|
|
572
|
+
else if (stage() === "leaving") callbacks.onAfterLeave?.();
|
|
573
|
+
complete();
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
watch(() => stage(), (currentStage) => {
|
|
577
|
+
const el = elementRef.current;
|
|
578
|
+
if (!el) return;
|
|
579
|
+
if (reducedMotion()) {
|
|
580
|
+
applyReducedMotion(currentStage, callbacks, complete);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (currentStage === "entering") {
|
|
584
|
+
callbacks.onEnter?.();
|
|
585
|
+
const frameId = applyEnter(el, config);
|
|
586
|
+
return () => cancelAnimationFrame(frameId);
|
|
587
|
+
}
|
|
588
|
+
if (currentStage === "leaving") {
|
|
589
|
+
callbacks.onLeave?.();
|
|
590
|
+
const frameId = applyLeave(el, config);
|
|
591
|
+
return () => cancelAnimationFrame(frameId);
|
|
592
|
+
}
|
|
593
|
+
if (currentStage === "entered") {
|
|
594
|
+
removeClasses(el, config.enter);
|
|
595
|
+
el.style.transition = "";
|
|
596
|
+
}
|
|
597
|
+
}, { immediate: true });
|
|
598
|
+
return /* @__PURE__ */ jsx(Show, {
|
|
599
|
+
when: shouldMount,
|
|
600
|
+
fallback: effectiveUnmount ? null : h(config.tag, {
|
|
601
|
+
ref: mergedRef,
|
|
602
|
+
...htmlProps,
|
|
603
|
+
style: {
|
|
604
|
+
...htmlProps.style ?? {},
|
|
605
|
+
display: "none"
|
|
606
|
+
}
|
|
607
|
+
}, children),
|
|
608
|
+
children: h(config.tag, {
|
|
609
|
+
ref: mergedRef,
|
|
610
|
+
...htmlProps
|
|
611
|
+
}, children)
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region src/kinetic/createKineticComponent.tsx
|
|
617
|
+
/** Keys that are kinetic-specific and should not be forwarded as HTML attrs. */
|
|
618
|
+
const KINETIC_KEYS = new Set([
|
|
619
|
+
"show",
|
|
620
|
+
"appear",
|
|
621
|
+
"unmount",
|
|
622
|
+
"timeout",
|
|
623
|
+
"transition",
|
|
624
|
+
"interval",
|
|
625
|
+
"reverseLeave",
|
|
626
|
+
"onEnter",
|
|
627
|
+
"onAfterEnter",
|
|
628
|
+
"onLeave",
|
|
629
|
+
"onAfterLeave"
|
|
630
|
+
]);
|
|
631
|
+
/**
|
|
632
|
+
* Core factory. Creates a component that delegates to the appropriate
|
|
633
|
+
* renderer based on config.mode, then attaches immutable chain methods
|
|
634
|
+
* via Object.assign.
|
|
635
|
+
*/
|
|
636
|
+
const createKineticComponent = (config) => {
|
|
637
|
+
const Component = (props) => {
|
|
638
|
+
const htmlProps = {};
|
|
639
|
+
const kineticProps = {};
|
|
640
|
+
for (const key in props) if (KINETIC_KEYS.has(key)) kineticProps[key] = props[key];
|
|
641
|
+
else htmlProps[key] = props[key];
|
|
642
|
+
const { show, appear, unmount, timeout, transition, interval, reverseLeave, onEnter, onAfterEnter, onLeave, onAfterLeave } = kineticProps;
|
|
643
|
+
const callbacks = {
|
|
644
|
+
onEnter: onEnter ?? config.onEnter,
|
|
645
|
+
onAfterEnter: onAfterEnter ?? config.onAfterEnter,
|
|
646
|
+
onLeave: onLeave ?? config.onLeave,
|
|
647
|
+
onAfterLeave: onAfterLeave ?? config.onAfterLeave
|
|
648
|
+
};
|
|
649
|
+
const { children, ...restHtml } = htmlProps;
|
|
650
|
+
if (config.mode === "collapse") return /* @__PURE__ */ jsx(CollapseRenderer, {
|
|
651
|
+
config,
|
|
652
|
+
htmlProps: restHtml,
|
|
653
|
+
show,
|
|
654
|
+
appear,
|
|
655
|
+
timeout,
|
|
656
|
+
transition,
|
|
657
|
+
callbacks,
|
|
658
|
+
children
|
|
659
|
+
});
|
|
660
|
+
if (config.mode === "stagger") return /* @__PURE__ */ jsx(StaggerRenderer, {
|
|
661
|
+
config,
|
|
662
|
+
htmlProps: restHtml,
|
|
663
|
+
show,
|
|
664
|
+
appear,
|
|
665
|
+
timeout,
|
|
666
|
+
interval,
|
|
667
|
+
reverseLeave,
|
|
668
|
+
callbacks,
|
|
669
|
+
children
|
|
670
|
+
});
|
|
671
|
+
if (config.mode === "group") return /* @__PURE__ */ jsx(GroupRenderer, {
|
|
672
|
+
config,
|
|
673
|
+
htmlProps: restHtml,
|
|
674
|
+
appear,
|
|
675
|
+
timeout,
|
|
676
|
+
callbacks,
|
|
677
|
+
children
|
|
678
|
+
});
|
|
679
|
+
return /* @__PURE__ */ jsx(TransitionRenderer, {
|
|
680
|
+
config,
|
|
681
|
+
htmlProps: restHtml,
|
|
682
|
+
show,
|
|
683
|
+
appear,
|
|
684
|
+
unmount,
|
|
685
|
+
timeout,
|
|
686
|
+
callbacks,
|
|
687
|
+
children
|
|
688
|
+
});
|
|
689
|
+
};
|
|
690
|
+
Component.displayName = `kinetic(${config.tag})`;
|
|
691
|
+
return Object.assign(Component, {
|
|
692
|
+
preset: (preset) => createKineticComponent({
|
|
693
|
+
...config,
|
|
694
|
+
...preset
|
|
695
|
+
}),
|
|
696
|
+
enter: (styles) => createKineticComponent({
|
|
697
|
+
...config,
|
|
698
|
+
enterStyle: styles
|
|
699
|
+
}),
|
|
700
|
+
enterTo: (styles) => createKineticComponent({
|
|
701
|
+
...config,
|
|
702
|
+
enterToStyle: styles
|
|
703
|
+
}),
|
|
704
|
+
enterTransition: (value) => createKineticComponent({
|
|
705
|
+
...config,
|
|
706
|
+
enterTransition: value
|
|
707
|
+
}),
|
|
708
|
+
leave: (styles) => createKineticComponent({
|
|
709
|
+
...config,
|
|
710
|
+
leaveStyle: styles
|
|
711
|
+
}),
|
|
712
|
+
leaveTo: (styles) => createKineticComponent({
|
|
713
|
+
...config,
|
|
714
|
+
leaveToStyle: styles
|
|
715
|
+
}),
|
|
716
|
+
leaveTransition: (value) => createKineticComponent({
|
|
717
|
+
...config,
|
|
718
|
+
leaveTransition: value
|
|
719
|
+
}),
|
|
720
|
+
enterClass: ({ active, from, to }) => createKineticComponent({
|
|
721
|
+
...config,
|
|
722
|
+
enter: active,
|
|
723
|
+
enterFrom: from,
|
|
724
|
+
enterTo: to
|
|
725
|
+
}),
|
|
726
|
+
leaveClass: ({ active, from, to }) => createKineticComponent({
|
|
727
|
+
...config,
|
|
728
|
+
leave: active,
|
|
729
|
+
leaveFrom: from,
|
|
730
|
+
leaveTo: to
|
|
731
|
+
}),
|
|
732
|
+
config: (opts) => createKineticComponent({
|
|
733
|
+
...config,
|
|
734
|
+
...opts
|
|
735
|
+
}),
|
|
736
|
+
on: (cbs) => createKineticComponent({
|
|
737
|
+
...config,
|
|
738
|
+
...cbs
|
|
739
|
+
}),
|
|
740
|
+
collapse: (opts) => createKineticComponent({
|
|
741
|
+
...config,
|
|
742
|
+
mode: "collapse",
|
|
743
|
+
...opts
|
|
744
|
+
}),
|
|
745
|
+
stagger: (opts) => createKineticComponent({
|
|
746
|
+
...config,
|
|
747
|
+
mode: "stagger",
|
|
748
|
+
...opts
|
|
749
|
+
}),
|
|
750
|
+
group: () => createKineticComponent({
|
|
751
|
+
...config,
|
|
752
|
+
mode: "group"
|
|
753
|
+
})
|
|
754
|
+
});
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region src/kinetic.ts
|
|
759
|
+
/**
|
|
760
|
+
* Creates a reusable animated component via immutable chaining.
|
|
761
|
+
*
|
|
762
|
+
* @example
|
|
763
|
+
* ```tsx
|
|
764
|
+
* // Transition (default)
|
|
765
|
+
* const FadeDiv = kinetic('div').preset(fade)
|
|
766
|
+
*
|
|
767
|
+
* // Collapse
|
|
768
|
+
* const Accordion = kinetic('div').collapse()
|
|
769
|
+
*
|
|
770
|
+
* // Stagger
|
|
771
|
+
* const StaggerList = kinetic('ul').preset(slideUp).stagger({ interval: 50 })
|
|
772
|
+
*
|
|
773
|
+
* // Group (key-based enter/exit)
|
|
774
|
+
* const AnimatedList = kinetic('ul').preset(fade).group()
|
|
775
|
+
* ```
|
|
776
|
+
*/
|
|
777
|
+
const kinetic = (tag) => createKineticComponent({
|
|
778
|
+
tag,
|
|
779
|
+
mode: "transition"
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
//#endregion
|
|
783
|
+
//#region src/presets.ts
|
|
784
|
+
const fade = {
|
|
785
|
+
enterStyle: { opacity: 0 },
|
|
786
|
+
enterToStyle: { opacity: 1 },
|
|
787
|
+
enterTransition: "opacity 300ms ease-out",
|
|
788
|
+
leaveStyle: { opacity: 1 },
|
|
789
|
+
leaveToStyle: { opacity: 0 },
|
|
790
|
+
leaveTransition: "opacity 200ms ease-in"
|
|
791
|
+
};
|
|
792
|
+
const scaleIn = {
|
|
793
|
+
enterStyle: {
|
|
794
|
+
opacity: 0,
|
|
795
|
+
transform: "scale(0.95)"
|
|
796
|
+
},
|
|
797
|
+
enterToStyle: {
|
|
798
|
+
opacity: 1,
|
|
799
|
+
transform: "scale(1)"
|
|
800
|
+
},
|
|
801
|
+
enterTransition: "opacity 300ms ease-out, transform 300ms ease-out",
|
|
802
|
+
leaveStyle: {
|
|
803
|
+
opacity: 1,
|
|
804
|
+
transform: "scale(1)"
|
|
805
|
+
},
|
|
806
|
+
leaveToStyle: {
|
|
807
|
+
opacity: 0,
|
|
808
|
+
transform: "scale(0.95)"
|
|
809
|
+
},
|
|
810
|
+
leaveTransition: "opacity 200ms ease-in, transform 200ms ease-in"
|
|
811
|
+
};
|
|
812
|
+
const slideUp = {
|
|
813
|
+
enterStyle: {
|
|
814
|
+
opacity: 0,
|
|
815
|
+
transform: "translateY(16px)"
|
|
816
|
+
},
|
|
817
|
+
enterToStyle: {
|
|
818
|
+
opacity: 1,
|
|
819
|
+
transform: "translateY(0)"
|
|
820
|
+
},
|
|
821
|
+
enterTransition: "opacity 300ms ease-out, transform 300ms ease-out",
|
|
822
|
+
leaveStyle: {
|
|
823
|
+
opacity: 1,
|
|
824
|
+
transform: "translateY(0)"
|
|
825
|
+
},
|
|
826
|
+
leaveToStyle: {
|
|
827
|
+
opacity: 0,
|
|
828
|
+
transform: "translateY(16px)"
|
|
829
|
+
},
|
|
830
|
+
leaveTransition: "opacity 200ms ease-in, transform 200ms ease-in"
|
|
831
|
+
};
|
|
832
|
+
const slideDown = {
|
|
833
|
+
enterStyle: {
|
|
834
|
+
opacity: 0,
|
|
835
|
+
transform: "translateY(-16px)"
|
|
836
|
+
},
|
|
837
|
+
enterToStyle: {
|
|
838
|
+
opacity: 1,
|
|
839
|
+
transform: "translateY(0)"
|
|
840
|
+
},
|
|
841
|
+
enterTransition: "opacity 300ms ease-out, transform 300ms ease-out",
|
|
842
|
+
leaveStyle: {
|
|
843
|
+
opacity: 1,
|
|
844
|
+
transform: "translateY(0)"
|
|
845
|
+
},
|
|
846
|
+
leaveToStyle: {
|
|
847
|
+
opacity: 0,
|
|
848
|
+
transform: "translateY(-16px)"
|
|
849
|
+
},
|
|
850
|
+
leaveTransition: "opacity 200ms ease-in, transform 200ms ease-in"
|
|
851
|
+
};
|
|
852
|
+
const slideLeft = {
|
|
853
|
+
enterStyle: {
|
|
854
|
+
opacity: 0,
|
|
855
|
+
transform: "translateX(16px)"
|
|
856
|
+
},
|
|
857
|
+
enterToStyle: {
|
|
858
|
+
opacity: 1,
|
|
859
|
+
transform: "translateX(0)"
|
|
860
|
+
},
|
|
861
|
+
enterTransition: "opacity 300ms ease-out, transform 300ms ease-out",
|
|
862
|
+
leaveStyle: {
|
|
863
|
+
opacity: 1,
|
|
864
|
+
transform: "translateX(0)"
|
|
865
|
+
},
|
|
866
|
+
leaveToStyle: {
|
|
867
|
+
opacity: 0,
|
|
868
|
+
transform: "translateX(16px)"
|
|
869
|
+
},
|
|
870
|
+
leaveTransition: "opacity 200ms ease-in, transform 200ms ease-in"
|
|
871
|
+
};
|
|
872
|
+
const slideRight = {
|
|
873
|
+
enterStyle: {
|
|
874
|
+
opacity: 0,
|
|
875
|
+
transform: "translateX(-16px)"
|
|
876
|
+
},
|
|
877
|
+
enterToStyle: {
|
|
878
|
+
opacity: 1,
|
|
879
|
+
transform: "translateX(0)"
|
|
880
|
+
},
|
|
881
|
+
enterTransition: "opacity 300ms ease-out, transform 300ms ease-out",
|
|
882
|
+
leaveStyle: {
|
|
883
|
+
opacity: 1,
|
|
884
|
+
transform: "translateX(0)"
|
|
885
|
+
},
|
|
886
|
+
leaveToStyle: {
|
|
887
|
+
opacity: 0,
|
|
888
|
+
transform: "translateX(-16px)"
|
|
889
|
+
},
|
|
890
|
+
leaveTransition: "opacity 200ms ease-in, transform 200ms ease-in"
|
|
891
|
+
};
|
|
892
|
+
const presets = {
|
|
893
|
+
fade,
|
|
894
|
+
scaleIn,
|
|
895
|
+
slideUp,
|
|
896
|
+
slideDown,
|
|
897
|
+
slideLeft,
|
|
898
|
+
slideRight
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
//#endregion
|
|
902
|
+
export { fade, kinetic, presets, scaleIn, slideDown, slideLeft, slideRight, slideUp, useAnimationEnd, useTransitionState };
|
|
903
|
+
//# sourceMappingURL=index.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pyreon/kinetic",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "CSS-transition-based animation components for Pyreon",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
"source": "./src/index.ts",
|
|
10
|
+
"import": "./lib/index.js",
|
|
11
|
+
"types": "./lib/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"types": "./lib/index.d.ts",
|
|
14
|
+
"main": "./lib/index.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"lib",
|
|
17
|
+
"!lib/**/*.map",
|
|
18
|
+
"!lib/analysis",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">= 18"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"prepublish": "bun run build",
|
|
30
|
+
"build": "bun run vl_rolldown_build",
|
|
31
|
+
"build:watch": "bun run vl_rolldown_build-watch",
|
|
32
|
+
"lint": "biome check src/",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:coverage": "vitest run --coverage",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@pyreon/core": ">=0.3.0",
|
|
40
|
+
"@pyreon/reactivity": ">=0.3.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@vitus-labs/tools-rolldown": "^1.15.0",
|
|
44
|
+
"@vitus-labs/tools-typescript": "^1.15.0"
|
|
45
|
+
}
|
|
46
|
+
}
|