@brandonsoccer22/gsap-editorial-carousel 0.1.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 +198 -0
- package/package.json +47 -0
- package/styles.css +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brandon Joyce
|
|
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,198 @@
|
|
|
1
|
+
# @brandonsoccer22/gsap-carousel
|
|
2
|
+
|
|
3
|
+
A small, class-driven GSAP carousel controller that handles state, accessibility, animation orchestration, and dot cloning without enforcing layout. You own the markup and CSS; the library handles wiring.
|
|
4
|
+
|
|
5
|
+
## Why class-driven animations?
|
|
6
|
+
Content editors can add animation classes and data attributes directly in a CMS without touching JavaScript. Elements inside slides opt into motion by adding a class that matches a registered animation.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @brandonsoccer22/gsap-carousel gsap
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Markup (semantic + user-placed controls)
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<section data-carousel>
|
|
18
|
+
<div class="hero">
|
|
19
|
+
<button data-carousel-prev>Prev</button>
|
|
20
|
+
<button data-carousel-next>Next</button>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="slides">
|
|
24
|
+
<article data-carousel-slide>
|
|
25
|
+
<h2 class="fade-up" data-seq="1">Headline</h2>
|
|
26
|
+
<p class="fade-in" data-seq="2" data-dur="0.4">Description</p>
|
|
27
|
+
</article>
|
|
28
|
+
|
|
29
|
+
<article data-carousel-slide>
|
|
30
|
+
<h2 class="slide-in" data-seq="1">Second</h2>
|
|
31
|
+
<p class="fade-in" data-seq="2">More copy</p>
|
|
32
|
+
</article>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div data-carousel-dots>
|
|
36
|
+
<button data-carousel-dot-template>•</button>
|
|
37
|
+
</div>
|
|
38
|
+
</section>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- The dot template is cloned once per slide and hidden via `template.hidden = true`.
|
|
42
|
+
- Controls can be placed anywhere inside the carousel markup; no wrappers are injected.
|
|
43
|
+
- Re-initialization clears previously generated dots (`data-carousel-dot=\"true\"`) before cloning.
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import "@brandonsoccer22/gsap-carousel/styles.css";
|
|
49
|
+
import { createCarousel } from "@brandonsoccer22/gsap-carousel";
|
|
50
|
+
|
|
51
|
+
const carousel = createCarousel("[data-carousel]", {
|
|
52
|
+
loop: true,
|
|
53
|
+
initialIndex: 0
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// programmatic control
|
|
57
|
+
carousel.next();
|
|
58
|
+
carousel.goTo(2, { immediate: false });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The base stylesheet only controls slide visibility (`.is-active`) and disabled controls; layout remains entirely up to you.
|
|
62
|
+
Optional helper: add `.gsap-carousel__stack` to a slides wrapper to stack slides with `position: absolute`.
|
|
63
|
+
Slides can use `data-carousel-slide` or the optional `.gsap-carousel__slide` class; both work with `.gsap-carousel__stack`.
|
|
64
|
+
|
|
65
|
+
## Transition lock behavior
|
|
66
|
+
While animating, all navigation requests are ignored and controls are disabled (buttons get `disabled`, everything gets `aria-disabled`).
|
|
67
|
+
|
|
68
|
+
## Keyboard input
|
|
69
|
+
Arrow keys are active only when focus is within the carousel root. The root is given `tabindex=\"0\"` if none exists.
|
|
70
|
+
|
|
71
|
+
## Built-in animations
|
|
72
|
+
Registered by default:
|
|
73
|
+
- `fade-in`
|
|
74
|
+
- `fade-up`
|
|
75
|
+
- `slide-in` (direction-aware)
|
|
76
|
+
|
|
77
|
+
If multiple classes match registered animation names, the first match in `classList` order wins.
|
|
78
|
+
|
|
79
|
+
## Custom animation registration
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { registerAnimation } from "@brandonsoccer22/gsap-carousel";
|
|
83
|
+
import type { AnimationFactory } from "@brandonsoccer22/gsap-carousel";
|
|
84
|
+
|
|
85
|
+
const popIn: AnimationFactory = ({ el, tl, opts }) => {
|
|
86
|
+
tl.fromTo(
|
|
87
|
+
el,
|
|
88
|
+
{ scale: 0.9, autoAlpha: 0 },
|
|
89
|
+
{ scale: 1, autoAlpha: 1, duration: opts.dur, delay: opts.delay, ease: opts.ease },
|
|
90
|
+
opts.at
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Optional reverse factory (used for exit when data-exit is missing)
|
|
95
|
+
popIn.reverse = ({ el, tl, opts }) => {
|
|
96
|
+
tl.to(el, { scale: 0.98, autoAlpha: 0, duration: opts.dur, delay: opts.delay }, opts.at);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
registerAnimation("pop-in", popIn);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Example with SplitText + overlap
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import type { AnimationFactory } from "@brandonsoccer22/gsap-carousel";
|
|
106
|
+
import { createCarousel, registerAnimation, registerGsapPlugins } from "@brandonsoccer22/gsap-carousel";
|
|
107
|
+
import { gsap } from "gsap";
|
|
108
|
+
import SplitText from "gsap/SplitText";
|
|
109
|
+
|
|
110
|
+
registerGsapPlugins(SplitText);
|
|
111
|
+
|
|
112
|
+
const splitLines: AnimationFactory = ({ el, tl, gsap, opts }) => {
|
|
113
|
+
const split = new SplitText(el, { type: "lines" });
|
|
114
|
+
|
|
115
|
+
gsap.set(split.lines, { yPercent: -120, opacity: 0 });
|
|
116
|
+
|
|
117
|
+
tl.to(
|
|
118
|
+
split.lines,
|
|
119
|
+
{
|
|
120
|
+
yPercent: 0,
|
|
121
|
+
opacity: 1,
|
|
122
|
+
duration: opts.dur,
|
|
123
|
+
ease: opts.ease,
|
|
124
|
+
stagger: 0.06
|
|
125
|
+
},
|
|
126
|
+
opts.at
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
tl.add(() => split.revert(), ">");
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
registerAnimation("split-lines", splitLines);
|
|
133
|
+
|
|
134
|
+
const splitLinesExit: AnimationFactory = ({ el, tl, opts }) => {
|
|
135
|
+
const split = new SplitText(el, { type: "lines" });
|
|
136
|
+
|
|
137
|
+
tl.to(
|
|
138
|
+
split.lines,
|
|
139
|
+
{
|
|
140
|
+
yPercent: 40,
|
|
141
|
+
opacity: 0,
|
|
142
|
+
duration: Math.min(opts.dur, 0.55),
|
|
143
|
+
ease: opts.ease,
|
|
144
|
+
stagger: 0.05
|
|
145
|
+
},
|
|
146
|
+
opts.at
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
tl.add(() => split.revert(), ">");
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
registerAnimation("split-lines-exit", splitLinesExit);
|
|
153
|
+
|
|
154
|
+
createCarousel("[data-carousel]", {
|
|
155
|
+
gsap,
|
|
156
|
+
transition: { overlap: 0.05 }
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Data attributes reference
|
|
161
|
+
- `data-seq="1"` sequence group (integer, default 1)
|
|
162
|
+
- `data-exit-seq="1"` exit sequence group (integer, defaults to `data-seq`)
|
|
163
|
+
- `data-dur="0.6"` duration in seconds (default from options)
|
|
164
|
+
- `data-delay="0"` delay in seconds (default 0)
|
|
165
|
+
- `data-at="-=0.4"` position offset in seconds for this element within its sequence; supports `+=`/`-=` or a plain number
|
|
166
|
+
- `data-ease="power2.out"` GSAP ease string
|
|
167
|
+
- `data-exit="fade-in"` explicit exit animation name
|
|
168
|
+
|
|
169
|
+
## Reduced motion
|
|
170
|
+
If the user prefers reduced motion, slide switching happens immediately and element-level enter/exit animations are skipped.
|
|
171
|
+
|
|
172
|
+
## Destroy
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
carousel.destroy();
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`destroy()` removes all listeners, kills active timelines, reverts GSAP context, and removes generated dots (the template is unhidden).
|
|
179
|
+
|
|
180
|
+
## API
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
createCarousel(root, options?): CarouselInstance
|
|
184
|
+
registerAnimation(name, factory): void
|
|
185
|
+
registerAnimations(map): void
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
interface CarouselInstance {
|
|
190
|
+
goTo(index: number, opts?: { immediate?: boolean }): void
|
|
191
|
+
next(opts?: { immediate?: boolean }): void
|
|
192
|
+
prev(opts?: { immediate?: boolean }): void
|
|
193
|
+
destroy(): void
|
|
194
|
+
getIndex(): number
|
|
195
|
+
getCount(): number
|
|
196
|
+
isAnimating(): boolean
|
|
197
|
+
}
|
|
198
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@brandonsoccer22/gsap-editorial-carousel",
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "git+https://github.com/brandonsoccer22/gsap-editorial-carousel.git"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.1.2",
|
|
8
|
+
"description": "A small, class-driven GSAP carousel controller with CMS-friendly animations.",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./dist/index.cjs",
|
|
11
|
+
"module": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.mjs",
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
18
|
+
},
|
|
19
|
+
"./styles.css": "./styles.css"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"styles.css",
|
|
24
|
+
"README.md",
|
|
25
|
+
"package.json"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"dev": "tsup --watch",
|
|
30
|
+
"clean": "rm -rf dist"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"gsap": "^3.12.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"gsap": "^3.12.0",
|
|
37
|
+
"tsup": "^8.0.1",
|
|
38
|
+
"typescript": "^5.4.0"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"gsap",
|
|
42
|
+
"carousel",
|
|
43
|
+
"animation",
|
|
44
|
+
"typescript"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT"
|
|
47
|
+
}
|
package/styles.css
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.is-disabled {
|
|
2
|
+
opacity: 0.5;
|
|
3
|
+
pointer-events: none;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* Base visibility only; layout remains user-controlled. */
|
|
7
|
+
[data-carousel] [data-carousel-slide],
|
|
8
|
+
[data-carousel] .gsap-carousel__slide {
|
|
9
|
+
opacity: 0;
|
|
10
|
+
pointer-events: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
[data-carousel] [data-carousel-slide].is-active,
|
|
14
|
+
[data-carousel] .gsap-carousel__slide.is-active {
|
|
15
|
+
opacity: 1;
|
|
16
|
+
pointer-events: auto;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Optional layout helper (.gsap-carousel__stack): opt in when you want slides stacked. */
|
|
20
|
+
.gsap-carousel__stack {
|
|
21
|
+
position: relative;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.gsap-carousel__stack [data-carousel-slide],
|
|
25
|
+
.gsap-carousel__stack .gsap-carousel__slide {
|
|
26
|
+
position: absolute;
|
|
27
|
+
inset: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Keep the active slide in flow so the stack can size to its content. */
|
|
31
|
+
.gsap-carousel__stack [data-carousel-slide].is-active {
|
|
32
|
+
position: relative;
|
|
33
|
+
inset: auto;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Set the point for the entire slide */
|
|
37
|
+
/* .is-animating {
|
|
38
|
+
cursor: progress;
|
|
39
|
+
} */
|