well-petal 0.0.22 → 0.0.24
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/demo/index.html +124 -0
- package/demo/styles.css +244 -0
- package/package.json +5 -2
- package/src/animation.ts +91 -83
- package/src/banner.ts +44 -0
- package/src/lib/animations.ts +76 -0
- package/src/lib/attributes.ts +48 -0
- package/src/lib/breakpoints.ts +4 -0
- package/src/lib/console.ts +51 -0
- package/src/lib/elements.ts +0 -0
- package/src/lib/helpers.ts +5 -0
- package/src/lib/memory.ts +54 -0
- package/src/petal.css +11 -0
- package/src/petal.ts +6 -93
- package/src/popup.ts +141 -0
- package/src/types/env.d.ts +1 -0
- package/webpack.config.js +15 -0
- package/dist/petal.js +0 -148
package/demo/index.html
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Petal Popup Demo</title>
|
|
7
|
+
<link rel="stylesheet" href="styles.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<!-- Banner at top of page -->
|
|
11
|
+
<div petal-el="banner" petal="demo-banner" petal-session-ttl="5" class="banner">
|
|
12
|
+
<div class="banner-content">
|
|
13
|
+
<p>🎉 Welcome to Petal! This banner remembers you closed it for 5 minutes (set via petal-session-ttl).</p>
|
|
14
|
+
<button petal-el="banner-close" class="banner-close" aria-label="Close banner">×</button>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="container">
|
|
19
|
+
<h1>Petal Popup Demo</h1>
|
|
20
|
+
<p>Click the buttons below to test different popup animations and configurations.</p>
|
|
21
|
+
|
|
22
|
+
<div class="buttons">
|
|
23
|
+
<!-- Scale Up Popup Trigger -->
|
|
24
|
+
<button petal-el="open" petal="scale-popup" class="btn">Open Scale Popup</button>
|
|
25
|
+
|
|
26
|
+
<!-- Slide Down Popup Trigger -->
|
|
27
|
+
<button petal-el="open" petal="slide-down-popup" class="btn">Open Slide Down Popup</button>
|
|
28
|
+
|
|
29
|
+
<!-- Slide Left Popup Trigger -->
|
|
30
|
+
<button petal-el="open" petal="slide-left-popup" class="btn">Open Slide Left Popup</button>
|
|
31
|
+
|
|
32
|
+
<!-- Mobile Responsive Popup Trigger -->
|
|
33
|
+
<button petal-el="open" petal="responsive-popup" class="btn">Open Responsive Popup</button>
|
|
34
|
+
|
|
35
|
+
<!-- Different Open/Close Animation Trigger -->
|
|
36
|
+
<button petal-el="open" petal="custom-mask-popup" class="btn">Open Different Animations Popup</button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- Scale Up Popup -->
|
|
41
|
+
<div petal-el="popup" petal="scale-popup" petal-anim-open="scale" petal-duration="0.5" petal-slot-opacity="0.5" class="popup">
|
|
42
|
+
<div petal-el="mask" petal-mask-opacity="0.15" class="mask"></div>
|
|
43
|
+
<div petal-el="slot" class="slot">
|
|
44
|
+
<div class="popup-content">
|
|
45
|
+
<h2>Scale Popup</h2>
|
|
46
|
+
<p>This popup uses a scale animation (auto-reverses on close).</p>
|
|
47
|
+
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
|
48
|
+
<button petal-el="close" petal="scale-popup" class="btn btn-close">Close</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<!-- Slide Down Popup -->
|
|
54
|
+
<div petal-el="popup" petal="slide-down-popup" petal-anim-open="slide-down" petal-duration="0.6" petal-mask-close="true" class="popup">
|
|
55
|
+
<div petal-el="mask" petal-mask-opacity="0.2" class="mask"></div>
|
|
56
|
+
<div petal-el="slot" class="slot">
|
|
57
|
+
<div class="popup-content">
|
|
58
|
+
<h2>Slide Down Popup</h2>
|
|
59
|
+
<p>This popup slides down from the top when opening, slides up to the top when closing.</p>
|
|
60
|
+
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
|
61
|
+
<button petal-el="close" petal="slide-down-popup" class="btn btn-close">Close</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<!-- Slide Left Popup -->
|
|
67
|
+
<div petal-el="popup" petal="slide-left-popup" petal-anim-open="slide-left" petal-duration="0.6" class="popup">
|
|
68
|
+
<div petal-el="mask" petal-mask-opacity="0.2" class="mask"></div>
|
|
69
|
+
<div petal-el="slot" class="slot">
|
|
70
|
+
<div class="popup-content">
|
|
71
|
+
<h2>Slide Left Popup</h2>
|
|
72
|
+
<p>This popup slides in from the left when opening, slides up to the top when closing.</p>
|
|
73
|
+
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
|
|
74
|
+
<button petal-el="close" petal="slide-left-popup" class="btn btn-close">Close</button>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Responsive Popup (Different animations for mobile/desktop) -->
|
|
80
|
+
<div petal-el="popup" petal="responsive-popup" petal-anim-open="scale" petal-anim-open-mobile="slide-up" petal-duration="0.5" class="popup">
|
|
81
|
+
<div petal-el="mask" petal-mask-opacity="0.3" class="mask"></div>
|
|
82
|
+
<div petal-el="slot" class="slot">
|
|
83
|
+
<div class="popup-content">
|
|
84
|
+
<h2>Responsive Popup</h2>
|
|
85
|
+
<p>This popup uses scale on desktop and slide-up (from bottom) on mobile.</p>
|
|
86
|
+
<p>Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.</p>
|
|
87
|
+
<button petal-el="close" petal="responsive-popup" class="btn btn-close">Close</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<!-- Different Open/Close Animations -->
|
|
93
|
+
<div petal-el="popup" petal="custom-mask-popup" petal-anim-open="scale" petal-anim-close="slide-up" petal-duration="0.5" class="popup">
|
|
94
|
+
<div petal-el="mask" petal-mask-opacity="0.6" class="mask"></div>
|
|
95
|
+
<div petal-el="slot" class="slot">
|
|
96
|
+
<div class="popup-content">
|
|
97
|
+
<h2>Different Open/Close Animations</h2>
|
|
98
|
+
<p>This popup scales when opening and slides down (to the bottom) when closing!</p>
|
|
99
|
+
<p>Darker background mask (opacity: 0.6).</p>
|
|
100
|
+
<button petal-el="close" petal="custom-mask-popup" class="btn btn-close">Close</button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<!-- Load GSAP -->
|
|
106
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
|
|
107
|
+
|
|
108
|
+
<!-- Load Petal -->
|
|
109
|
+
<script src="../dist/petal.js"></script>
|
|
110
|
+
|
|
111
|
+
<!-- Initialize Petal -->
|
|
112
|
+
<script>
|
|
113
|
+
// Initialize all popups and banners when page loads
|
|
114
|
+
if (window.petal) {
|
|
115
|
+
if (window.petal.initializeAllPopups) {
|
|
116
|
+
window.petal.initializeAllPopups();
|
|
117
|
+
}
|
|
118
|
+
if (window.petal.initializeBanner) {
|
|
119
|
+
window.petal.initializeBanner();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
</script>
|
|
123
|
+
</body>
|
|
124
|
+
</html>
|
package/demo/styles.css
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/* Reset and Base Styles */
|
|
2
|
+
* {
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 0;
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
10
|
+
line-height: 1.6;
|
|
11
|
+
color: #333;
|
|
12
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
padding: 20px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Banner Styles */
|
|
18
|
+
.banner {
|
|
19
|
+
position: fixed;
|
|
20
|
+
top: 0;
|
|
21
|
+
left: 0;
|
|
22
|
+
right: 0;
|
|
23
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
24
|
+
color: white;
|
|
25
|
+
z-index: 10000;
|
|
26
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.banner-content {
|
|
30
|
+
max-width: 1200px;
|
|
31
|
+
margin: 0 auto;
|
|
32
|
+
padding: 12px 20px;
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: space-between;
|
|
36
|
+
gap: 20px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.banner-content p {
|
|
40
|
+
margin: 0;
|
|
41
|
+
color: white;
|
|
42
|
+
font-size: 14px;
|
|
43
|
+
font-weight: 500;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.banner-close {
|
|
47
|
+
background: rgba(255, 255, 255, 0.2);
|
|
48
|
+
border: none;
|
|
49
|
+
color: white;
|
|
50
|
+
font-size: 24px;
|
|
51
|
+
width: 32px;
|
|
52
|
+
height: 32px;
|
|
53
|
+
border-radius: 50%;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
transition: all 0.2s ease;
|
|
59
|
+
flex-shrink: 0;
|
|
60
|
+
line-height: 1;
|
|
61
|
+
padding: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.banner-close:hover {
|
|
65
|
+
background: rgba(255, 255, 255, 0.3);
|
|
66
|
+
transform: scale(1.1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.banner-close:active {
|
|
70
|
+
transform: scale(0.95);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Adjust body padding when banner is visible */
|
|
74
|
+
body {
|
|
75
|
+
padding-top: 70px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Container */
|
|
79
|
+
.container {
|
|
80
|
+
max-width: 800px;
|
|
81
|
+
margin: 0 auto;
|
|
82
|
+
background: white;
|
|
83
|
+
padding: 40px;
|
|
84
|
+
border-radius: 12px;
|
|
85
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
h1 {
|
|
89
|
+
color: #667eea;
|
|
90
|
+
margin-bottom: 10px;
|
|
91
|
+
font-size: 2.5rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
p {
|
|
95
|
+
color: #666;
|
|
96
|
+
margin-bottom: 20px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* Buttons */
|
|
100
|
+
.buttons {
|
|
101
|
+
display: flex;
|
|
102
|
+
flex-wrap: wrap;
|
|
103
|
+
gap: 15px;
|
|
104
|
+
margin-top: 30px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.btn {
|
|
108
|
+
padding: 12px 24px;
|
|
109
|
+
background: #667eea;
|
|
110
|
+
color: white;
|
|
111
|
+
border: none;
|
|
112
|
+
border-radius: 8px;
|
|
113
|
+
font-size: 16px;
|
|
114
|
+
font-weight: 600;
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
transition: all 0.3s ease;
|
|
117
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.btn:hover {
|
|
121
|
+
background: #5568d3;
|
|
122
|
+
transform: translateY(-2px);
|
|
123
|
+
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.4);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.btn:active {
|
|
127
|
+
transform: translateY(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Popup Structure */
|
|
131
|
+
.popup {
|
|
132
|
+
position: fixed;
|
|
133
|
+
top: 0;
|
|
134
|
+
left: 0;
|
|
135
|
+
width: 100%;
|
|
136
|
+
height: 100%;
|
|
137
|
+
display: none;
|
|
138
|
+
align-items: center;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
z-index: 9999;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Mask (Background Overlay) */
|
|
144
|
+
.mask {
|
|
145
|
+
position: absolute;
|
|
146
|
+
top: 0;
|
|
147
|
+
left: 0;
|
|
148
|
+
width: 100%;
|
|
149
|
+
height: 100%;
|
|
150
|
+
background: black;
|
|
151
|
+
opacity: 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Slot (Popup Content Container) */
|
|
155
|
+
.slot {
|
|
156
|
+
position: relative;
|
|
157
|
+
z-index: 10000;
|
|
158
|
+
max-width: 90%;
|
|
159
|
+
max-height: 90vh;
|
|
160
|
+
overflow: auto;
|
|
161
|
+
opacity: 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Popup Content */
|
|
165
|
+
.popup-content {
|
|
166
|
+
background: white;
|
|
167
|
+
padding: 40px;
|
|
168
|
+
border-radius: 16px;
|
|
169
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
170
|
+
max-width: 500px;
|
|
171
|
+
width: 100%;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.popup-content h2 {
|
|
175
|
+
color: #667eea;
|
|
176
|
+
margin-bottom: 15px;
|
|
177
|
+
font-size: 2rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.popup-content p {
|
|
181
|
+
margin-bottom: 15px;
|
|
182
|
+
line-height: 1.8;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.popup-content p:last-of-type {
|
|
186
|
+
margin-bottom: 25px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Close Button */
|
|
190
|
+
.btn-close {
|
|
191
|
+
background: #dc3545;
|
|
192
|
+
width: 100%;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.btn-close:hover {
|
|
196
|
+
background: #c82333;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/* Mobile Responsive */
|
|
200
|
+
@media (max-width: 768px) {
|
|
201
|
+
.container {
|
|
202
|
+
padding: 20px;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
h1 {
|
|
206
|
+
font-size: 2rem;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.buttons {
|
|
210
|
+
flex-direction: column;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.btn {
|
|
214
|
+
width: 100%;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.popup-content {
|
|
218
|
+
padding: 30px 20px;
|
|
219
|
+
max-width: 95%;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.popup-content h2 {
|
|
223
|
+
font-size: 1.5rem;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Scrollbar Styling for Popup Slot */
|
|
228
|
+
.slot::-webkit-scrollbar {
|
|
229
|
+
width: 8px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.slot::-webkit-scrollbar-track {
|
|
233
|
+
background: #f1f1f1;
|
|
234
|
+
border-radius: 10px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.slot::-webkit-scrollbar-thumb {
|
|
238
|
+
background: #667eea;
|
|
239
|
+
border-radius: 10px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.slot::-webkit-scrollbar-thumb:hover {
|
|
243
|
+
background: #5568d3;
|
|
244
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "well-petal",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "Webflow Popups powered by attributes",
|
|
5
5
|
"main": "petal.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
8
|
"build": "webpack --config webpack.config.js",
|
|
9
|
+
"dev": "webpack serve --config webpack.config.js",
|
|
9
10
|
"tsc": "tsc --noEmit"
|
|
10
11
|
},
|
|
11
12
|
"keywords": [],
|
|
@@ -24,7 +25,9 @@
|
|
|
24
25
|
"prettier": "^3.5.3",
|
|
25
26
|
"ts-loader": "^9.5.1",
|
|
26
27
|
"typescript": "^5.7.3",
|
|
27
|
-
"webpack
|
|
28
|
+
"webpack": "^5.96.1",
|
|
29
|
+
"webpack-cli": "^5.1.4",
|
|
30
|
+
"webpack-dev-server": "^5.2.0"
|
|
28
31
|
},
|
|
29
32
|
"peerDependencies": {
|
|
30
33
|
"gsap": "^3.13.0"
|
package/src/animation.ts
CHANGED
|
@@ -1,92 +1,99 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
animateScaleUp,
|
|
3
|
+
animateOpenSlideUp,
|
|
4
|
+
animateOpenSlideDown,
|
|
5
|
+
animateOpenSlideLeft,
|
|
6
|
+
animateOpenSlideRight,
|
|
7
|
+
animateScaleDown,
|
|
8
|
+
animateCloseSlideUp,
|
|
9
|
+
animateCloseSlideDown,
|
|
10
|
+
animateCloseSlideLeft,
|
|
11
|
+
animateCloseSlideRight,
|
|
12
|
+
} from "./lib/animations";
|
|
13
|
+
import { ATTR_PETAL_ANIM_OPEN, ATTR_PETAL_ANIM_CLOSE, ATTR_PETAL_ANIM_OPEN_MOBILE, ATTR_PETAL_ANIM_CLOSE_MOBILE, ATTR_PETAL_DURATION } from "./lib/attributes";
|
|
14
|
+
import { isMobile } from "./lib/breakpoints";
|
|
5
15
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"slide-
|
|
16
|
+
// Popup animation types
|
|
17
|
+
export type PopupAnimation = "scale" | "slide-up" | "slide-down" | "slide-left" | "slide-right";
|
|
18
|
+
const validAnimations: PopupAnimation[] = ["scale", "slide-up", "slide-down", "slide-left", "slide-right"];
|
|
19
|
+
|
|
20
|
+
const animationOpenMap: Record<PopupAnimation, () => GSAPTweenVars> = {
|
|
21
|
+
scale: animateScaleUp,
|
|
22
|
+
"slide-up": animateOpenSlideUp,
|
|
23
|
+
"slide-down": animateOpenSlideDown,
|
|
24
|
+
"slide-left": animateOpenSlideLeft,
|
|
25
|
+
"slide-right": animateOpenSlideRight,
|
|
13
26
|
};
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const [normal, reverse] = animationMap[animation];
|
|
24
|
-
const gsapAnim = open ? reverse(false) : normal(true);
|
|
25
|
-
gsapAnim.to.duration = duration;
|
|
26
|
-
return gsapAnim;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Individual animations
|
|
30
|
-
function animateScaleUp(show: boolean): GSAPTweenVars {
|
|
31
|
-
return {
|
|
32
|
-
from: { scale: 0 },
|
|
33
|
-
to: { scale: 1, opacity: show ? 1 : 0, ease: "power3.inOut" },
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function animateScaleDown(show: boolean): GSAPTweenVars {
|
|
38
|
-
return {
|
|
39
|
-
from: { scale: 1 },
|
|
40
|
-
to: { scale: 0, opacity: show ? 1 : 0, ease: "power3.inOut" },
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function animateSlideUp(show: boolean): GSAPTweenVars {
|
|
45
|
-
return {
|
|
46
|
-
from: { y: "100%", x: "0%" },
|
|
47
|
-
to: { y: "0%", opacity: show ? 1 : 0, ease: "power3.inOut" },
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function animateSlideDown(show: boolean): GSAPTweenVars {
|
|
52
|
-
return {
|
|
53
|
-
from: { y: "0%", x: "0%" },
|
|
54
|
-
to: { y: "100%", opacity: show ? 1 : 0, ease: "power3.inOut" },
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function animateSlideRight(show: boolean): GSAPTweenVars {
|
|
59
|
-
return {
|
|
60
|
-
from: { x: "0%", y: "0%" },
|
|
61
|
-
to: { x: "100%", opacity: show ? 1 : 0, ease: "power3.inOut" },
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function animateSlideLeft(show: boolean): GSAPTweenVars {
|
|
66
|
-
return {
|
|
67
|
-
from: { x: "100%", y: "0%" },
|
|
68
|
-
to: { x: "0%", opacity: show ? 1 : 0, ease: "power3.inOut" },
|
|
69
|
-
};
|
|
70
|
-
}
|
|
28
|
+
const animationCloseMap: Record<PopupAnimation, () => GSAPTweenVars> = {
|
|
29
|
+
scale: animateScaleDown,
|
|
30
|
+
"slide-up": animateCloseSlideUp,
|
|
31
|
+
"slide-down": animateCloseSlideDown,
|
|
32
|
+
"slide-left": animateCloseSlideLeft,
|
|
33
|
+
"slide-right": animateCloseSlideRight,
|
|
34
|
+
};
|
|
71
35
|
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
36
|
+
// Map open animations to their corresponding close animations
|
|
37
|
+
const reverseAnimationMap: Record<PopupAnimation, PopupAnimation> = {
|
|
38
|
+
scale: "scale",
|
|
39
|
+
"slide-up": "slide-down",
|
|
40
|
+
"slide-down": "slide-up",
|
|
41
|
+
"slide-left": "slide-right",
|
|
42
|
+
"slide-right": "slide-left",
|
|
43
|
+
};
|
|
79
44
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Creates GSAP animation based on the popup's attributes
|
|
47
|
+
* @param popup The Popup element
|
|
48
|
+
* @param direction Open or Close
|
|
49
|
+
* @returns GSAP animation vars for the popup
|
|
50
|
+
*/
|
|
51
|
+
export function getPopupGSAPAnimation(popup: HTMLElement, direction: "open" | "close"): GSAPTweenVars {
|
|
52
|
+
// Determine the name of the animation to perform
|
|
53
|
+
let animationName = getAnimationName(popup, direction);
|
|
54
|
+
|
|
55
|
+
// Get the animation function
|
|
56
|
+
let anim;
|
|
57
|
+
if (direction === "open") anim = animationOpenMap[animationName]();
|
|
58
|
+
else anim = animationCloseMap[animationName]();
|
|
59
|
+
|
|
60
|
+
// Set the animation duration
|
|
61
|
+
const duration = parseFloat(popup.getAttribute(ATTR_PETAL_DURATION) || "0.5");
|
|
62
|
+
anim.to.duration = duration;
|
|
63
|
+
|
|
64
|
+
return anim;
|
|
85
65
|
}
|
|
86
66
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
67
|
+
/**
|
|
68
|
+
*
|
|
69
|
+
* @param popup The Popup element
|
|
70
|
+
* @param direction Open or Close
|
|
71
|
+
* @returns The name of the animation to perform
|
|
72
|
+
*/
|
|
73
|
+
function getAnimationName(popup: HTMLElement, direction: "open" | "close"): PopupAnimation {
|
|
74
|
+
const openDesktop = getPopupAnimation(popup, ATTR_PETAL_ANIM_OPEN);
|
|
75
|
+
const openMobile = getPopupAnimation(popup, ATTR_PETAL_ANIM_OPEN_MOBILE);
|
|
76
|
+
|
|
77
|
+
const closeDesktop = getPopupAnimation(popup, ATTR_PETAL_ANIM_CLOSE);
|
|
78
|
+
const closeMobile = getPopupAnimation(popup, ATTR_PETAL_ANIM_CLOSE_MOBILE);
|
|
79
|
+
|
|
80
|
+
if (direction === "open") {
|
|
81
|
+
if (isMobile()) return openMobile;
|
|
82
|
+
else return openDesktop;
|
|
83
|
+
}
|
|
84
|
+
// If closing
|
|
85
|
+
else {
|
|
86
|
+
const hasCloseDesktop = popup.hasAttribute(ATTR_PETAL_ANIM_CLOSE);
|
|
87
|
+
const hasCloseMobile = popup.hasAttribute(ATTR_PETAL_ANIM_CLOSE_MOBILE);
|
|
88
|
+
|
|
89
|
+
if (isMobile()) {
|
|
90
|
+
// If no mobile close animation is set, use the reverse of the open animation
|
|
91
|
+
return hasCloseMobile ? closeMobile : reverseAnimationMap[openMobile];
|
|
92
|
+
} else {
|
|
93
|
+
// If no desktop close animation is set, use the reverse of the open animation
|
|
94
|
+
return hasCloseDesktop ? closeDesktop : reverseAnimationMap[openDesktop];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
// Validate popup animation string
|
|
@@ -95,6 +102,7 @@ export function isPopupAnimation(value: string): value is PopupAnimation {
|
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
// Safe parser with fallback
|
|
98
|
-
function getPopupAnimation(
|
|
105
|
+
function getPopupAnimation(popup: HTMLElement, attr: string): PopupAnimation {
|
|
106
|
+
const raw = popup.getAttribute(attr);
|
|
99
107
|
return isPopupAnimation(raw ?? "") ? (raw as PopupAnimation) : "slide-up";
|
|
100
108
|
}
|
package/src/banner.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ATTR_PETAL_BANNER, ATTR_PETAL_BANNER_CLOSE, ATTR_PETAL_NAME, ATTR_PETAL_ELEMENT, ATTR_PETAL_SESSION_TTL, PetalElements } from "./lib/attributes";
|
|
2
|
+
import { getAllPetalElementsOfType } from "./lib/helpers";
|
|
3
|
+
import { storeClosedState, checkClosedState } from "./lib/memory";
|
|
4
|
+
|
|
5
|
+
export function initializeBanner() {
|
|
6
|
+
const banners = getAllPetalElementsOfType(ATTR_PETAL_BANNER);
|
|
7
|
+
console.log(`🌸 Detected ${banners.length} banner(s)`);
|
|
8
|
+
|
|
9
|
+
banners.forEach((banner) => {
|
|
10
|
+
const name = banner.getAttribute(ATTR_PETAL_NAME);
|
|
11
|
+
if (!name) return;
|
|
12
|
+
console.log(` → Banner: "${name}"`);
|
|
13
|
+
|
|
14
|
+
const closeButtons = banner.querySelectorAll(`[${ATTR_PETAL_ELEMENT}="${ATTR_PETAL_BANNER_CLOSE}"]`);
|
|
15
|
+
|
|
16
|
+
// Get session TTL in minutes (default: 1440 minutes = 24 hours)
|
|
17
|
+
const sessionTTLMinutes = parseFloat(banner.getAttribute(ATTR_PETAL_SESSION_TTL) || "1440");
|
|
18
|
+
|
|
19
|
+
// Create PetalElements object for memory functions
|
|
20
|
+
const petal: PetalElements = {
|
|
21
|
+
name,
|
|
22
|
+
trigger: banner,
|
|
23
|
+
popup: banner as HTMLElement,
|
|
24
|
+
mask: banner,
|
|
25
|
+
slot: banner
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Check if banner was closed and session is still valid
|
|
29
|
+
if (checkClosedState("banner", petal, sessionTTLMinutes)) {
|
|
30
|
+
// If closed and session valid, hide the banner
|
|
31
|
+
(banner as HTMLElement).style.display = "none";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Add click event listeners to close buttons
|
|
35
|
+
closeButtons.forEach((closeButton) => {
|
|
36
|
+
closeButton.addEventListener("click", () => {
|
|
37
|
+
// Hide the banner
|
|
38
|
+
(banner as HTMLElement).style.display = "none";
|
|
39
|
+
// Store closed state in sessionStorage with timestamp
|
|
40
|
+
storeClosedState("banner", petal);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|