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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export function animateScaleUp(): GSAPTweenVars {
|
|
2
|
+
return {
|
|
3
|
+
from: { scale: 0, opacity: 0 },
|
|
4
|
+
to: { scale: 1, opacity: 1, ease: "power3.inOut" },
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export function animateScaleDown(): GSAPTweenVars {
|
|
8
|
+
return {
|
|
9
|
+
from: { scale: 1, opacity: 0 },
|
|
10
|
+
to: { scale: 0, opacity: 1, ease: "power3.inOut" },
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function animateOpenSlideUp(): GSAPTweenVars {
|
|
14
|
+
return {
|
|
15
|
+
from: { y: "100%", x: "0%", opacity: 0 }, // From bottom
|
|
16
|
+
to: { y: "0%", x: "0%", opacity: 1, ease: "power3.inOut" }, // To top
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function animateOpenSlideDown(): GSAPTweenVars {
|
|
20
|
+
return {
|
|
21
|
+
from: { y: "-100%", x: "0%", opacity: 0 }, // From top
|
|
22
|
+
to: { y: "0%", x: "0%", opacity: 1, ease: "power3.inOut" }, // To bottom
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function animateOpenSlideRight(): GSAPTweenVars {
|
|
26
|
+
return {
|
|
27
|
+
from: { x: "-100%", y: "0%", opacity: 0 }, // From left
|
|
28
|
+
to: { x: "0%", y: "0%", opacity: 1, ease: "power3.inOut" }, // To center
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function animateOpenSlideLeft(): GSAPTweenVars {
|
|
32
|
+
return {
|
|
33
|
+
from: { x: "100%", y: "0%", opacity: 0 }, // From right
|
|
34
|
+
to: { x: "0%", y: "0%", opacity: 1, ease: "power3.inOut" }, // To center
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function animateCloseSlideUp(): GSAPTweenVars {
|
|
38
|
+
return {
|
|
39
|
+
from: { y: "0%", x: "0%", opacity: 1 }, // From bottom
|
|
40
|
+
to: { y: "-100%", x: "0%", opacity: 0, ease: "power3.inOut" }, // To top
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function animateCloseSlideDown(): GSAPTweenVars {
|
|
44
|
+
return {
|
|
45
|
+
from: { y: "0%", x: "0%", opacity: 1 }, // From top
|
|
46
|
+
to: { y: "100%", x: "0%", opacity: 0, ease: "power3.inOut" }, // To bottom
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function animateCloseSlideRight(): GSAPTweenVars {
|
|
50
|
+
return {
|
|
51
|
+
from: { x: "0%", y: "0%", opacity: 1 }, // From left
|
|
52
|
+
to: { x: "100%", y: "0%", opacity: 0, ease: "power3.inOut" }, // To center
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function animateCloseSlideLeft(): GSAPTweenVars {
|
|
56
|
+
return {
|
|
57
|
+
from: { x: "0%", y: "0%", opacity: 1 }, // From right
|
|
58
|
+
to: { x: "-100%", y: "0%", opacity: 0, ease: "power3.inOut" }, // To center
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Mask animations
|
|
63
|
+
|
|
64
|
+
export function animateMaskOpen(opacity: number): GSAPTweenVars {
|
|
65
|
+
return {
|
|
66
|
+
from: { opacity: 0 },
|
|
67
|
+
to: { opacity: opacity, duration: 0.5 },
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function animateMaskClosed(opacity: number): GSAPTweenVars {
|
|
72
|
+
return {
|
|
73
|
+
from: { opacity: opacity },
|
|
74
|
+
to: { opacity: 0, duration: 0.5 },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface PetalElements {
|
|
2
|
+
name: string;
|
|
3
|
+
trigger: Element;
|
|
4
|
+
popup: HTMLElement;
|
|
5
|
+
mask: Element;
|
|
6
|
+
slot: Element;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// BASE
|
|
10
|
+
export const ATTR_PETAL_NAME = "petal";
|
|
11
|
+
export const ATTR_PETAL_ELEMENT = "petal-el";
|
|
12
|
+
|
|
13
|
+
// BEHAVIOR
|
|
14
|
+
export const ATTR_PETAL_SHOW_ONCE = "petal-show-once"; // Regardless of other settings, only show the popup once per user session
|
|
15
|
+
export const ATTR_PETAL_SHOW_DELAY = "petal-show-delay"; // Time to wait before showing popup (in seconds)
|
|
16
|
+
export const ATTR_PETAL_SESSION_TTL = "petal-session-ttl"; // Time to keep user session (in hours)
|
|
17
|
+
|
|
18
|
+
/**-------------------------*
|
|
19
|
+
* POPUP
|
|
20
|
+
*--------------------------*/
|
|
21
|
+
|
|
22
|
+
// ELEMENTS
|
|
23
|
+
export const ATTR_PETAL_POPUP = "popup";
|
|
24
|
+
export const ATTR_PETAL_OPEN = "open";
|
|
25
|
+
export const ATTR_PETAL_CLOSE = "close";
|
|
26
|
+
export const ATTR_PETAL_MASK = "mask";
|
|
27
|
+
export const ATTR_PETAL_SLOT = "slot";
|
|
28
|
+
|
|
29
|
+
// MASK
|
|
30
|
+
export const ATTR_PETAL_MASK_OPACITY = "petal-mask-opacity";
|
|
31
|
+
export const ATTR_PETAL_MASK_CLOSE = "petal-mask-close";
|
|
32
|
+
|
|
33
|
+
// ANIMATIONS
|
|
34
|
+
export const ATTR_PETAL_ANIM_OPEN = "petal-anim-open";
|
|
35
|
+
export const ATTR_PETAL_ANIM_CLOSE = "petal-anim-close";
|
|
36
|
+
export const ATTR_PETAL_ANIM_OPEN_MOBILE = "petal-anim-open-mobile";
|
|
37
|
+
export const ATTR_PETAL_ANIM_CLOSE_MOBILE = "petal-anim-close-mobile";
|
|
38
|
+
export const ATTR_PETAL_DURATION = "petal-duration";
|
|
39
|
+
|
|
40
|
+
/**-------------------------*
|
|
41
|
+
* BANNER
|
|
42
|
+
*--------------------------*/
|
|
43
|
+
|
|
44
|
+
// ELEMENTS
|
|
45
|
+
export const ATTR_PETAL_BANNER = "banner";
|
|
46
|
+
export const ATTR_PETAL_BANNER_CLOSE = "banner-close";
|
|
47
|
+
|
|
48
|
+
export const ATTR_PETAL_BANNER_CLOSED_CLASS = "petal-hide-nav-banner";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Custom Error classes for Petal
|
|
2
|
+
export class PetalError extends Error {
|
|
3
|
+
constructor(
|
|
4
|
+
message: string,
|
|
5
|
+
public readonly element?: Element,
|
|
6
|
+
public readonly petalName?: string
|
|
7
|
+
) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "PetalError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class MissingNameError extends PetalError {
|
|
14
|
+
constructor(trigger: Element) {
|
|
15
|
+
super('Trigger is missing the "petal" attribute', trigger);
|
|
16
|
+
this.name = "MissingNameError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class MissingPopupError extends PetalError {
|
|
21
|
+
constructor(popupName: string, trigger: Element) {
|
|
22
|
+
super(`Popup with name "${popupName}" not found`, trigger, popupName);
|
|
23
|
+
this.name = "MissingPopupError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class MissingTriggerError extends PetalError {
|
|
28
|
+
constructor(popupName: string, trigger: Element) {
|
|
29
|
+
super(`Trigger error for "${popupName}"`, trigger, popupName);
|
|
30
|
+
this.name = "MissingTriggerError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class MissingMaskError extends PetalError {
|
|
35
|
+
constructor(popupName: string, trigger: Element) {
|
|
36
|
+
super(`Mask not found for "${popupName}"`, trigger, popupName);
|
|
37
|
+
this.name = "MissingMaskError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class MissingSlotError extends PetalError {
|
|
42
|
+
constructor(popupName: string, trigger: Element) {
|
|
43
|
+
super(`Slot not found for "${popupName}"`, trigger, popupName);
|
|
44
|
+
this.name = "MissingSlotError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Helper function to log errors (for backward compatibility or simple logging)
|
|
49
|
+
export function logPetalError(error: PetalError): void {
|
|
50
|
+
console.error(`[${error.name}]:`, error.message, error.element);
|
|
51
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { PetalElements } from "./attributes";
|
|
2
|
+
|
|
3
|
+
// MEMORY
|
|
4
|
+
type MemoryItem = "popup" | "banner";
|
|
5
|
+
|
|
6
|
+
export function storeClosedState(type: MemoryItem, petal: PetalElements) {
|
|
7
|
+
const { name } = petal;
|
|
8
|
+
const now = new Date();
|
|
9
|
+
sessionStorage.setItem(getMemoryKey(type, name), now.getTime().toString());
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check Closed State in Session Storage
|
|
14
|
+
* @param type The type of element (popup or banner)
|
|
15
|
+
* @param petal The Petal to check
|
|
16
|
+
* @param sessionTTLMinutes The length that the session is valid for (in minutes)
|
|
17
|
+
* @returns True if the element has been closed in this session and the session is still valid, false otherwise
|
|
18
|
+
*/
|
|
19
|
+
export function checkClosedState(type: MemoryItem, petal: PetalElements, sessionTTLMinutes: number): boolean {
|
|
20
|
+
const { name } = petal;
|
|
21
|
+
const timestampStr = sessionStorage.getItem(getMemoryKey(type, name));
|
|
22
|
+
if (!timestampStr) return false;
|
|
23
|
+
|
|
24
|
+
const timestamp = parseInt(timestampStr, 10);
|
|
25
|
+
if (isNaN(timestamp)) {
|
|
26
|
+
sessionStorage.removeItem(getMemoryKey(type, name));
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const itemTime = new Date(timestamp);
|
|
32
|
+
const diffMinutes = (now.getTime() - itemTime.getTime()) / (1000 * 60);
|
|
33
|
+
|
|
34
|
+
if (diffMinutes > sessionTTLMinutes) {
|
|
35
|
+
// Session expired
|
|
36
|
+
sessionStorage.removeItem(getMemoryKey(type, name));
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Legacy function names for backward compatibility
|
|
44
|
+
export function storePopupClosedState(petal: PetalElements) {
|
|
45
|
+
storeClosedState("popup", petal);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function checkPopupClosedState(petal: PetalElements, sessionTTLMinutes: number): boolean {
|
|
49
|
+
return checkClosedState("popup", petal, sessionTTLMinutes);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getMemoryKey(key: MemoryItem, name: string) {
|
|
53
|
+
return `petal_memory_${key}_${name}`;
|
|
54
|
+
}
|
package/src/petal.css
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** BANNER **/
|
|
2
|
+
.petal-hide-nav-banner [petal-el="banner"] {
|
|
3
|
+
display: none;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** NAV **/
|
|
7
|
+
|
|
8
|
+
/* Disable scrolling when mobile nav is open */
|
|
9
|
+
body:has([petal-el="nav"] .w-nav-button.w--open):not(:has([petal-el="nav-desktop"]:not(.w-condition-invisible))) {
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
}
|
package/src/petal.ts
CHANGED
|
@@ -1,95 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { pauseVideo } from "./video";
|
|
1
|
+
import { initializeAllPopups, initializePopupTriggers } from "./popup";
|
|
2
|
+
import { initializeBanner } from "./banner";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
export type ErrorType = "NO_NAME" | "NO_TRIGGER" | "NO_POPUP" | "NO_MASK" | "NO_SLOT";
|
|
4
|
+
console.log(`🌸 Hello from Wellflow Petal v${APP_VERSION}`);
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (!popupName) {
|
|
12
|
-
logError("NO_NAME", "", trigger);
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const popup = document.querySelector(`[petal='${popupName}'][petal-el='popup']`) as HTMLElement | null;
|
|
17
|
-
if (!popup) {
|
|
18
|
-
logError("NO_POPUP", popupName, trigger);
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Popup Elements
|
|
23
|
-
const mask = popup.querySelector("[petal-el='mask']");
|
|
24
|
-
const slot = popup.querySelector("[petal-el='slot']");
|
|
25
|
-
|
|
26
|
-
// Mask Settings
|
|
27
|
-
const maskOpacity = parseFloat(mask?.getAttribute("petal-mask-opacity") || "0.15");
|
|
28
|
-
|
|
29
|
-
// Initialize GSAP timeline
|
|
30
|
-
const tl = gsap.timeline();
|
|
31
|
-
|
|
32
|
-
// Initialize Popup
|
|
33
|
-
gsap.set(slot, { opacity: 0 });
|
|
34
|
-
|
|
35
|
-
trigger.addEventListener("click", () => animate(popup));
|
|
36
|
-
|
|
37
|
-
// Animate open/close
|
|
38
|
-
function animate(popup: HTMLElement): void {
|
|
39
|
-
if (!slot) console.warn("A slot was not found for this popup.");
|
|
40
|
-
|
|
41
|
-
// Get the animation
|
|
42
|
-
const open = getComputedStyle(popup).display !== "none";
|
|
43
|
-
const anim = getAnimationNew(popup, open);
|
|
44
|
-
console.log(anim);
|
|
45
|
-
|
|
46
|
-
console.log(`[Petal Popup]`, {
|
|
47
|
-
popupName,
|
|
48
|
-
popup,
|
|
49
|
-
slot,
|
|
50
|
-
maskOpacity,
|
|
51
|
-
open,
|
|
52
|
-
anim,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// If the popup is closed, open it
|
|
56
|
-
if (!open) {
|
|
57
|
-
// Show Popup
|
|
58
|
-
tl.set(popup, { display: "flex" });
|
|
59
|
-
// Animate Mask Open
|
|
60
|
-
if (mask) {
|
|
61
|
-
tl.fromTo(mask, animateMaskOpen(maskOpacity).from, animateMaskOpen(maskOpacity).to, "<");
|
|
62
|
-
}
|
|
63
|
-
// Animate the Slot Open
|
|
64
|
-
tl.fromTo(slot, anim.from, anim.to);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// If the popup is open, close it
|
|
68
|
-
else {
|
|
69
|
-
// Pause any videos in the popup
|
|
70
|
-
pauseVideo(popup);
|
|
71
|
-
// Animate the Slot Closed
|
|
72
|
-
tl.fromTo(slot, anim.from, anim.to);
|
|
73
|
-
// Animate Mask Closed
|
|
74
|
-
if (mask) {
|
|
75
|
-
tl.fromTo(mask, animateMaskClosed(maskOpacity).from, animateMaskClosed(maskOpacity).to, "<");
|
|
76
|
-
}
|
|
77
|
-
// Hide the Popup
|
|
78
|
-
tl.set(popup, { display: "none" });
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Centralized error logging
|
|
84
|
-
function logError(error: ErrorType, popupName: string, trigger: Element): void {
|
|
85
|
-
switch (error) {
|
|
86
|
-
case "NO_NAME":
|
|
87
|
-
console.error(`Trigger is missing the "petal" attribute:`, trigger);
|
|
88
|
-
break;
|
|
89
|
-
case "NO_POPUP":
|
|
90
|
-
console.error(`Popup with name "${popupName}" not found for trigger:`, trigger);
|
|
91
|
-
break;
|
|
92
|
-
default:
|
|
93
|
-
console.error(`Popup error [${error}] for "${popupName}":`, trigger);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
6
|
+
initializeAllPopups();
|
|
7
|
+
initializePopupTriggers();
|
|
8
|
+
initializeBanner();
|
package/src/popup.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { gsap } from "gsap";
|
|
2
|
+
import { getPopupGSAPAnimation } from "./animation";
|
|
3
|
+
import { MissingMaskError, MissingNameError, MissingPopupError, MissingSlotError, PetalError } from "./lib/console";
|
|
4
|
+
import { animateMaskOpen, animateMaskClosed } from "./lib/animations";
|
|
5
|
+
import { PetalElements, ATTR_PETAL_MASK_OPACITY, ATTR_PETAL_OPEN, ATTR_PETAL_CLOSE, ATTR_PETAL_MASK_CLOSE, ATTR_PETAL_ELEMENT, ATTR_PETAL_POPUP, ATTR_PETAL_NAME } from "./lib/attributes";
|
|
6
|
+
import { getAllPetalElementsOfType } from "./lib/helpers";
|
|
7
|
+
import { storePopupClosedState } from "./lib/memory";
|
|
8
|
+
import { pauseVideo } from "./video";
|
|
9
|
+
|
|
10
|
+
export function initializeAllPopups(): void {
|
|
11
|
+
const popups = getAllPetalElementsOfType(ATTR_PETAL_POPUP);
|
|
12
|
+
|
|
13
|
+
console.log(`🌸 Detected ${popups.length} popup(s)`);
|
|
14
|
+
|
|
15
|
+
popups.forEach((popup) => {
|
|
16
|
+
const popupElement = popup as HTMLElement;
|
|
17
|
+
const name = popup.getAttribute(ATTR_PETAL_NAME) || "unknown";
|
|
18
|
+
console.log(` → Popup: "${name}"`);
|
|
19
|
+
|
|
20
|
+
const mask = findPopupElement(popup, "mask") as HTMLElement | null;
|
|
21
|
+
const slot = findPopupElement(popup, "slot") as HTMLElement | null;
|
|
22
|
+
|
|
23
|
+
// Set slot opacity to 0
|
|
24
|
+
if (slot) slot.style.opacity = "0";
|
|
25
|
+
|
|
26
|
+
// Set mask opacity to 0
|
|
27
|
+
if (mask) mask.style.opacity = "0";
|
|
28
|
+
|
|
29
|
+
// Set popup display to none
|
|
30
|
+
popupElement.style.display = "none";
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function openPopup(petal: PetalElements): void {
|
|
35
|
+
const { name, popup, slot, mask } = petal;
|
|
36
|
+
const tl = gsap.timeline();
|
|
37
|
+
|
|
38
|
+
// Set Popup display to flex
|
|
39
|
+
tl.set(popup, { display: "flex" });
|
|
40
|
+
// Animate Mask open
|
|
41
|
+
const maskOpacity = parseFloat(mask?.getAttribute(ATTR_PETAL_MASK_OPACITY) || "0.15");
|
|
42
|
+
tl.fromTo(mask, animateMaskOpen(maskOpacity).from, animateMaskOpen(maskOpacity).to, "<");
|
|
43
|
+
// Animate Slot Open
|
|
44
|
+
const anim = getPopupGSAPAnimation(popup, "open");
|
|
45
|
+
tl.set(slot, { clearProps: "x,y,scale,transform" });
|
|
46
|
+
tl.fromTo(slot, anim.from, anim.to);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function closePopup(petal: PetalElements): void {
|
|
50
|
+
const { name, popup, slot, mask } = petal;
|
|
51
|
+
const tl = gsap.timeline();
|
|
52
|
+
|
|
53
|
+
// VIDEO: Pause any videos inside the popup
|
|
54
|
+
pauseVideo(popup);
|
|
55
|
+
// SESSION: Store popup closed state
|
|
56
|
+
storePopupClosedState(petal);
|
|
57
|
+
// Animate the Slot Closed
|
|
58
|
+
const anim = getPopupGSAPAnimation(popup, "close");
|
|
59
|
+
tl.fromTo(slot, anim.from, anim.to);
|
|
60
|
+
// Animate Mask Closed
|
|
61
|
+
if (mask) tl.to(mask, animateMaskClosed(0).to, "<");
|
|
62
|
+
|
|
63
|
+
// Hide the Popup and clear transforms so they don't persist
|
|
64
|
+
tl.set(popup, { display: "none" });
|
|
65
|
+
tl.set(slot, { clearProps: "x,y,scale,transform" });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function initializePopupTriggers(): void {
|
|
69
|
+
// Initialize Popup Open Triggers
|
|
70
|
+
const openTriggers = getAllPetalElementsOfType(ATTR_PETAL_OPEN);
|
|
71
|
+
console.log(`🌸 Detected ${openTriggers.length} open trigger(s)`);
|
|
72
|
+
forEachPopupTrigger(openTriggers, (petal) => {
|
|
73
|
+
const { trigger } = petal;
|
|
74
|
+
console.log(` → Open trigger for: "${petal.name}"`);
|
|
75
|
+
trigger.addEventListener("click", () => openPopup(petal));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Initialize Popup Close Triggers
|
|
79
|
+
const closeTriggers = getAllPetalElementsOfType(ATTR_PETAL_CLOSE);
|
|
80
|
+
console.log(`🌸 Detected ${closeTriggers.length} close trigger(s)`);
|
|
81
|
+
forEachPopupTrigger(closeTriggers, (petal) => {
|
|
82
|
+
const { trigger } = petal;
|
|
83
|
+
console.log(` → Close trigger for: "${petal.name}"`);
|
|
84
|
+
trigger.addEventListener("click", () => closePopup(petal));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Initialize Mask Close Triggers (masks with petal-mask-close="true")
|
|
88
|
+
const popups = getAllPetalElementsOfType(ATTR_PETAL_POPUP);
|
|
89
|
+
popups.forEach((popup) => {
|
|
90
|
+
const mask = findPopupElement(popup, "mask") as HTMLElement | null;
|
|
91
|
+
const maskClose = popup.getAttribute(ATTR_PETAL_MASK_CLOSE) === "true";
|
|
92
|
+
if (mask && maskClose) {
|
|
93
|
+
mask.addEventListener("click", () => {
|
|
94
|
+
const slot = findPopupElement(popup, "slot") as HTMLElement | null;
|
|
95
|
+
const name = popup.getAttribute(ATTR_PETAL_NAME) || popup.id;
|
|
96
|
+
if (slot) {
|
|
97
|
+
closePopup({ name, popup: popup as HTMLElement, slot, mask, trigger: mask });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function findPetal(el: Element): HTMLElement {
|
|
105
|
+
const popupName = el.getAttribute(ATTR_PETAL_NAME);
|
|
106
|
+
if (!popupName) throw new MissingNameError(el);
|
|
107
|
+
|
|
108
|
+
const popup = document.querySelector(`[${ATTR_PETAL_NAME}='${popupName}'][${ATTR_PETAL_ELEMENT}='${ATTR_PETAL_POPUP}']`) as HTMLElement | null;
|
|
109
|
+
if (!popup) throw new MissingPopupError(popupName, el);
|
|
110
|
+
|
|
111
|
+
return popup;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function findPopupElement(popup: Element, attr: string): HTMLElement | null {
|
|
115
|
+
return popup.querySelector(`[${ATTR_PETAL_ELEMENT}='${attr}']`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Helper to iterate over triggers with error handling
|
|
119
|
+
export function forEachPopupTrigger(triggers: NodeListOf<Element>, callback: (petal: PetalElements) => void): void {
|
|
120
|
+
triggers.forEach((trigger) => {
|
|
121
|
+
try {
|
|
122
|
+
const popup = findPetal(trigger);
|
|
123
|
+
const name = trigger.getAttribute(ATTR_PETAL_NAME) || "unknown";
|
|
124
|
+
|
|
125
|
+
const mask = popup.querySelector(`[${ATTR_PETAL_ELEMENT}='mask']`) as HTMLElement;
|
|
126
|
+
if (!mask) throw new MissingMaskError(name, trigger);
|
|
127
|
+
|
|
128
|
+
const slot = popup.querySelector(`[${ATTR_PETAL_ELEMENT}='slot']`) as HTMLElement;
|
|
129
|
+
if (!slot) throw new MissingSlotError(name, trigger);
|
|
130
|
+
|
|
131
|
+
callback({ name, trigger: trigger as HTMLElement, popup, mask, slot });
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error instanceof PetalError) {
|
|
134
|
+
// Log and skip invalid triggers
|
|
135
|
+
console.error(`[${error.name}]:`, error.message, error.element);
|
|
136
|
+
} else {
|
|
137
|
+
throw error; // Re-throw unexpected errors
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare const APP_VERSION: string;
|
package/webpack.config.js
CHANGED
|
@@ -40,4 +40,19 @@ module.exports = {
|
|
|
40
40
|
APP_VERSION: JSON.stringify(require('./package.json').version),
|
|
41
41
|
}),
|
|
42
42
|
],
|
|
43
|
+
devServer: {
|
|
44
|
+
static: [
|
|
45
|
+
{
|
|
46
|
+
directory: path.join(__dirname, 'demo'),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
directory: path.join(__dirname, 'dist'),
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
compress: true,
|
|
53
|
+
port: 8080,
|
|
54
|
+
hot: true,
|
|
55
|
+
open: true,
|
|
56
|
+
watchFiles: ['src/**/*.ts', 'demo/**/*'],
|
|
57
|
+
},
|
|
43
58
|
};
|