@anubis609/astroanimate-core 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 +212 -0
- package/dist/components/AnimatedBorderButton/AnimatedBorderButton.astro +129 -0
- package/dist/components/AnimatedBorderButton/index.js +3 -0
- package/dist/components/AnimatedBorderButton/index.js.map +1 -0
- package/dist/components/AnimatedButton/AnimatedButton.astro +299 -0
- package/dist/components/AnimatedButton/index.js +3 -0
- package/dist/components/AnimatedButton/index.js.map +1 -0
- package/dist/components/AnimatedCard/AnimatedCard.astro +832 -0
- package/dist/components/AnimatedCard/index.js +3 -0
- package/dist/components/AnimatedCard/index.js.map +1 -0
- package/dist/components/AnimatedTabs/AnimatedTabs.astro +348 -0
- package/dist/components/AnimatedTabs/index.js +3 -0
- package/dist/components/AnimatedTabs/index.js.map +1 -0
- package/dist/components/ArrowCTAButton/ArrowCTAButton.astro +159 -0
- package/dist/components/ArticleCard/ArticleCard.astro +208 -0
- package/dist/components/CardStack/CardStack.astro +444 -0
- package/dist/components/CardStack/index.js +3 -0
- package/dist/components/CardStack/index.js.map +1 -0
- package/dist/components/CountUp/CountUp.astro +89 -0
- package/dist/components/CountUp/index.js +3 -0
- package/dist/components/CountUp/index.js.map +1 -0
- package/dist/components/Dock/Dock.astro +567 -0
- package/dist/components/Dock/DockItem.astro +135 -0
- package/dist/components/Dropdown/Dropdown.astro +264 -0
- package/dist/components/ExpandableCard/ExpandableCard.astro +402 -0
- package/dist/components/ExpandableCard/index.js +3 -0
- package/dist/components/ExpandableCard/index.js.map +1 -0
- package/dist/components/FadeInText/FadeInText.astro +314 -0
- package/dist/components/FadeInText/index.js +3 -0
- package/dist/components/FadeInText/index.js.map +1 -0
- package/dist/components/FillHoverButton/FillHoverButton.astro +125 -0
- package/dist/components/GitHubShineButton/GitHubShineButton.astro +208 -0
- package/dist/components/GlassCard/GlassCard.astro +245 -0
- package/dist/components/GlassCard/index.js +3 -0
- package/dist/components/GlassCard/index.js.map +1 -0
- package/dist/components/GridDotsBackground/GridDotsBackground.astro +144 -0
- package/dist/components/HighlightText/HighlightText.astro +106 -0
- package/dist/components/InfiniteMarquee/InfiniteMarquee.astro +339 -0
- package/dist/components/JobCard/JobCard.astro +230 -0
- package/dist/components/LiquidGlassCard/LiquidGlassCard.astro +569 -0
- package/dist/components/Loader/Loader.astro +156 -0
- package/dist/components/Loader/index.js +3 -0
- package/dist/components/Loader/index.js.map +1 -0
- package/dist/components/NewsletterPopupCard/NewsletterPopupCard.astro +331 -0
- package/dist/components/ProductReviewCard/ProductReviewCard.astro +188 -0
- package/dist/components/ProgressBar/ProgressBar.astro +137 -0
- package/dist/components/ProgressBar/index.js +3 -0
- package/dist/components/ProgressBar/index.js.map +1 -0
- package/dist/components/RevealImage/RevealImage.astro +160 -0
- package/dist/components/RevealImage/index.js +3 -0
- package/dist/components/RevealImage/index.js.map +1 -0
- package/dist/components/ScaleIn/ScaleIn.astro +231 -0
- package/dist/components/ScaleIn/index.js +3 -0
- package/dist/components/ScaleIn/index.js.map +1 -0
- package/dist/components/SlidingOverlayButton/SlidingOverlayButton.astro +126 -0
- package/dist/components/StaggerTextButton/StaggerTextButton.astro +132 -0
- package/dist/components/Tooltip/Tooltip.astro +255 -0
- package/dist/components/Tooltip/index.js +3 -0
- package/dist/components/Tooltip/index.js.map +1 -0
- package/dist/components/TypewriterText/TypewriterText.astro +380 -0
- package/dist/components/TypewriterText/index.js +3 -0
- package/dist/components/TypewriterText/index.js.map +1 -0
- package/dist/components/index.js +33 -0
- package/dist/components/index.js.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/countup.js +90 -0
- package/dist/internal/countup.js.map +1 -0
- package/dist/internal/dropdown.js +166 -0
- package/dist/internal/dropdown.js.map +1 -0
- package/dist/internal/fadein.js +116 -0
- package/dist/internal/fadein.js.map +1 -0
- package/dist/internal/guards.js +12 -0
- package/dist/internal/guards.js.map +1 -0
- package/dist/internal/tabs.js +140 -0
- package/dist/internal/tabs.js.map +1 -0
- package/package.json +229 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export { default as AnimatedTabs } from './components/AnimatedTabs/AnimatedTabs.astro';
|
|
2
|
+
export { default as AnimatedButton } from './components/AnimatedButton/AnimatedButton.astro';
|
|
3
|
+
export { default as AnimatedCard } from './components/AnimatedCard/AnimatedCard.astro';
|
|
4
|
+
export { default as ExpandableCard } from './components/ExpandableCard/ExpandableCard.astro';
|
|
5
|
+
export { default as FadeInText } from './components/FadeInText/FadeInText.astro';
|
|
6
|
+
export { default as Loader } from './components/Loader/Loader.astro';
|
|
7
|
+
export { default as ProgressBar } from './components/ProgressBar/ProgressBar.astro';
|
|
8
|
+
export { default as RevealImage } from './components/RevealImage/RevealImage.astro';
|
|
9
|
+
export { default as ScaleIn } from './components/ScaleIn/ScaleIn.astro';
|
|
10
|
+
export { default as Tooltip } from './components/Tooltip/Tooltip.astro';
|
|
11
|
+
export { default as TypewriterText } from './components/TypewriterText/TypewriterText.astro';
|
|
12
|
+
export { default as CardStack } from './components/CardStack/CardStack.astro';
|
|
13
|
+
export { default as CountUp } from './components/CountUp/CountUp.astro';
|
|
14
|
+
export { default as Dock } from './components/Dock/Dock.astro';
|
|
15
|
+
export { default as DockItem } from './components/Dock/DockItem.astro';
|
|
16
|
+
export { default as Dropdown } from './components/Dropdown/Dropdown.astro';
|
|
17
|
+
export { default as GridDotsBackground } from './components/GridDotsBackground/GridDotsBackground.astro';
|
|
18
|
+
export { default as HighlightText } from './components/HighlightText/HighlightText.astro';
|
|
19
|
+
export { default as InfiniteMarquee } from './components/InfiniteMarquee/InfiniteMarquee.astro';
|
|
20
|
+
export { default as LiquidGlassCard } from './components/LiquidGlassCard/LiquidGlassCard.astro';
|
|
21
|
+
export { default as AnimatedBorderButton } from './components/AnimatedBorderButton/AnimatedBorderButton.astro';
|
|
22
|
+
export { default as ArrowCTAButton } from './components/ArrowCTAButton/ArrowCTAButton.astro';
|
|
23
|
+
export { default as SlidingOverlayButton } from './components/SlidingOverlayButton/SlidingOverlayButton.astro';
|
|
24
|
+
export { default as FillHoverButton } from './components/FillHoverButton/FillHoverButton.astro';
|
|
25
|
+
export { default as GitHubShineButton } from './components/GitHubShineButton/GitHubShineButton.astro';
|
|
26
|
+
export { default as StaggerTextButton } from './components/StaggerTextButton/StaggerTextButton.astro';
|
|
27
|
+
export { default as JobCard } from './components/JobCard/JobCard.astro';
|
|
28
|
+
export { default as ProductReviewCard } from './components/ProductReviewCard/ProductReviewCard.astro';
|
|
29
|
+
export { default as ArticleCard } from './components/ArticleCard/ArticleCard.astro';
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/internal/guards.ts
|
|
2
|
+
function canEnhance() {
|
|
3
|
+
if (typeof window === "undefined") return false;
|
|
4
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/internal/countup.ts
|
|
11
|
+
var initialized = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
var activeAnimations = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
var activeObservers = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
function enhanceCountUp(root) {
|
|
15
|
+
if (!canEnhance()) return;
|
|
16
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
17
|
+
if (initialized.has(root)) return;
|
|
18
|
+
const value = parseFloat(root.dataset.value || "0");
|
|
19
|
+
const start = parseFloat(root.dataset.start || "0");
|
|
20
|
+
const duration = parseInt(root.dataset.duration || "2000");
|
|
21
|
+
const decimals = parseInt(root.dataset.decimals || "0");
|
|
22
|
+
const prefix = root.dataset.prefix || "";
|
|
23
|
+
const suffix = root.dataset.suffix || "";
|
|
24
|
+
const format = (num) => {
|
|
25
|
+
return prefix + num.toLocaleString(void 0, {
|
|
26
|
+
minimumFractionDigits: decimals,
|
|
27
|
+
maximumFractionDigits: decimals
|
|
28
|
+
}) + suffix;
|
|
29
|
+
};
|
|
30
|
+
let cleanupObserver;
|
|
31
|
+
const animate = () => {
|
|
32
|
+
let startTime = null;
|
|
33
|
+
function step(timestamp) {
|
|
34
|
+
if (!startTime) startTime = timestamp;
|
|
35
|
+
const progress = Math.min((timestamp - startTime) / duration, 1);
|
|
36
|
+
const ease = progress * (2 - progress);
|
|
37
|
+
const current = start + (value - start) * ease;
|
|
38
|
+
root.textContent = format(current);
|
|
39
|
+
if (progress < 1) {
|
|
40
|
+
const id2 = requestAnimationFrame(step);
|
|
41
|
+
activeAnimations.set(root, id2);
|
|
42
|
+
} else {
|
|
43
|
+
root.textContent = format(value);
|
|
44
|
+
activeAnimations.delete(root);
|
|
45
|
+
if (cleanupObserver) cleanupObserver.disconnect();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const id = requestAnimationFrame(step);
|
|
49
|
+
activeAnimations.set(root, id);
|
|
50
|
+
};
|
|
51
|
+
const observer = new IntersectionObserver(
|
|
52
|
+
(entries) => {
|
|
53
|
+
entries.forEach((entry) => {
|
|
54
|
+
if (entry.isIntersecting) {
|
|
55
|
+
animate();
|
|
56
|
+
observer.disconnect();
|
|
57
|
+
activeObservers.delete(root);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
{ threshold: parseFloat(root.dataset.threshold || "0.1") }
|
|
62
|
+
);
|
|
63
|
+
observer.observe(root);
|
|
64
|
+
activeObservers.set(root, observer);
|
|
65
|
+
initialized.set(root, true);
|
|
66
|
+
cleanupObserver = new MutationObserver(() => {
|
|
67
|
+
if (!document.contains(root)) {
|
|
68
|
+
const animId = activeAnimations.get(root);
|
|
69
|
+
if (typeof animId === "number") {
|
|
70
|
+
cancelAnimationFrame(animId);
|
|
71
|
+
}
|
|
72
|
+
const obs = activeObservers.get(root);
|
|
73
|
+
if (obs) {
|
|
74
|
+
obs.disconnect();
|
|
75
|
+
}
|
|
76
|
+
cleanupObserver.disconnect();
|
|
77
|
+
initialized.delete(root);
|
|
78
|
+
activeAnimations.delete(root);
|
|
79
|
+
activeObservers.delete(root);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
cleanupObserver.observe(document.body, {
|
|
83
|
+
childList: true,
|
|
84
|
+
subtree: true
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { enhanceCountUp };
|
|
89
|
+
//# sourceMappingURL=countup.js.map
|
|
90
|
+
//# sourceMappingURL=countup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/guards.ts","../../src/internal/countup.ts"],"names":["id"],"mappings":"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","file":"countup.js","sourcesContent":["export function canEnhance(): boolean {\n if (typeof window === \"undefined\") return false;\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n return false;\n }\n return true;\n}\n","import { canEnhance } from \"./guards\";\n\n// ✅ CRITERION 3: WeakMaps for safe state tracking across instances\nconst initialized = new WeakMap<HTMLElement, boolean>();\nconst activeAnimations = new WeakMap<HTMLElement, number>();\nconst activeObservers = new WeakMap<HTMLElement, IntersectionObserver>();\n\nexport function enhanceCountUp(root: HTMLElement) {\n if (!canEnhance()) return;\n if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;\n\n if (initialized.has(root)) return;\n\n const value = parseFloat(root.dataset.value || \"0\");\n const start = parseFloat(root.dataset.start || \"0\");\n const duration = parseInt(root.dataset.duration || \"2000\");\n const decimals = parseInt(root.dataset.decimals || \"0\");\n const prefix = root.dataset.prefix || \"\";\n const suffix = root.dataset.suffix || \"\";\n\n /**\n * ✅ CRITERION 8: Helper to format values consistently between baseline and JS\n */\n const format = (num: number) => {\n return (\n prefix +\n num.toLocaleString(undefined, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n }) +\n suffix\n );\n };\n\n /**\n * Main animation loop using rAF\n */\n // eslint-disable-next-line prefer-const\n let cleanupObserver: MutationObserver;\n\n const animate = () => {\n let startTime: number | null = null;\n\n function step(timestamp: number) {\n if (!startTime) startTime = timestamp;\n const progress = Math.min((timestamp - startTime) / duration, 1);\n\n // Quadratic Out Easing: f(t) = t(2-t)\n const ease = progress * (2 - progress);\n const current = start + (value - start) * ease;\n\n // ✅ CRITERION 7: JS only updates content (text), not styles\n root.textContent = format(current);\n\n if (progress < 1) {\n const id = requestAnimationFrame(step);\n activeAnimations.set(root, id);\n } else {\n root.textContent = format(value); // Final snap for precision\n activeAnimations.delete(root);\n if (cleanupObserver) cleanupObserver.disconnect();\n }\n }\n\n const id = requestAnimationFrame(step);\n activeAnimations.set(root, id);\n };\n\n // ✅ CRITERION 3: IntersectionObserver for scroll-based trigger\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n animate();\n observer.disconnect(); // Only run once\n activeObservers.delete(root);\n }\n });\n },\n { threshold: parseFloat(root.dataset.threshold || \"0.1\") }\n );\n\n observer.observe(root);\n activeObservers.set(root, observer);\n initialized.set(root, true);\n\n /**\n * ✅ CRITERION 3: Cleanup mechanism\n * Handles cases where component is removed from DOM (e.g., View Transitions or client-side routing)\n */\n cleanupObserver = new MutationObserver(() => {\n if (!document.contains(root)) {\n // Cleanup Timer/rAF\n const animId = activeAnimations.get(root);\n if (typeof animId === \"number\") {\n cancelAnimationFrame(animId);\n }\n\n // Cleanup Observer\n const obs = activeObservers.get(root);\n if (obs) {\n obs.disconnect();\n }\n\n // Cleanup Self\n cleanupObserver.disconnect();\n\n // Cleanup State\n initialized.delete(root);\n activeAnimations.delete(root);\n activeObservers.delete(root);\n }\n });\n\n cleanupObserver.observe(document.body, {\n childList: true,\n subtree: true,\n });\n}\n"]}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// src/internal/guards.ts
|
|
2
|
+
function canEnhance() {
|
|
3
|
+
if (typeof window === "undefined") return false;
|
|
4
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/internal/dropdown.ts
|
|
11
|
+
var initialized = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
function enhanceDropdown(root) {
|
|
13
|
+
if (!canEnhance()) return;
|
|
14
|
+
if (initialized.has(root)) return;
|
|
15
|
+
const trigger = root.querySelector("[data-dropdown-trigger]");
|
|
16
|
+
const content = root.querySelector("[data-dropdown-content]");
|
|
17
|
+
if (!trigger || !content) return;
|
|
18
|
+
root.dataset.ready = "true";
|
|
19
|
+
const isOpen = () => root.dataset.state === "open";
|
|
20
|
+
const triggerMode = root.dataset.trigger ?? "click";
|
|
21
|
+
const hoverDelay = parseInt(root.dataset.hoverDelay ?? "120", 10);
|
|
22
|
+
const open = () => {
|
|
23
|
+
root.dataset.state = "open";
|
|
24
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
25
|
+
content.setAttribute("aria-hidden", "false");
|
|
26
|
+
};
|
|
27
|
+
const openWithFocus = () => {
|
|
28
|
+
open();
|
|
29
|
+
const firstItem = content.querySelector(
|
|
30
|
+
"button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])"
|
|
31
|
+
);
|
|
32
|
+
firstItem?.focus();
|
|
33
|
+
};
|
|
34
|
+
const close = () => {
|
|
35
|
+
root.dataset.state = "closed";
|
|
36
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
37
|
+
content.setAttribute("aria-hidden", "true");
|
|
38
|
+
};
|
|
39
|
+
const toggle = () => isOpen() ? close() : openWithFocus();
|
|
40
|
+
const handleItemClick = (e) => {
|
|
41
|
+
const target = e.target;
|
|
42
|
+
const item = target.closest(".menu-item");
|
|
43
|
+
if (item) {
|
|
44
|
+
const indexStr = item.dataset.index;
|
|
45
|
+
if (indexStr) {
|
|
46
|
+
const event = new CustomEvent("dropdown-select", {
|
|
47
|
+
detail: { index: parseInt(indexStr, 10), item }
|
|
48
|
+
});
|
|
49
|
+
root.dispatchEvent(event);
|
|
50
|
+
}
|
|
51
|
+
close();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
let cleanupFn;
|
|
55
|
+
if (triggerMode === "hover") {
|
|
56
|
+
let closeTimer = null;
|
|
57
|
+
const cancelClose = () => {
|
|
58
|
+
if (closeTimer !== null) {
|
|
59
|
+
clearTimeout(closeTimer);
|
|
60
|
+
closeTimer = null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const scheduleClose = () => {
|
|
64
|
+
cancelClose();
|
|
65
|
+
closeTimer = setTimeout(close, hoverDelay);
|
|
66
|
+
};
|
|
67
|
+
const handleRootEnter = () => {
|
|
68
|
+
cancelClose();
|
|
69
|
+
open();
|
|
70
|
+
};
|
|
71
|
+
const handleRootLeave = () => {
|
|
72
|
+
scheduleClose();
|
|
73
|
+
};
|
|
74
|
+
const handleTriggerKeydown = (e) => {
|
|
75
|
+
if (e.key === "Escape") {
|
|
76
|
+
close();
|
|
77
|
+
trigger.focus();
|
|
78
|
+
}
|
|
79
|
+
if ((e.key === "Enter" || e.key === " " || e.key === "ArrowDown") && !isOpen()) {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
openWithFocus();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const handleContentKeydown = (e) => {
|
|
85
|
+
if (e.key === "Escape") {
|
|
86
|
+
close();
|
|
87
|
+
trigger.focus();
|
|
88
|
+
}
|
|
89
|
+
if (e.key === "Tab") {
|
|
90
|
+
close();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
root.addEventListener("mouseenter", handleRootEnter);
|
|
94
|
+
root.addEventListener("mouseleave", handleRootLeave);
|
|
95
|
+
trigger.addEventListener("keydown", handleTriggerKeydown);
|
|
96
|
+
content.addEventListener("keydown", handleContentKeydown);
|
|
97
|
+
content.addEventListener("click", handleItemClick);
|
|
98
|
+
cleanupFn = () => {
|
|
99
|
+
root.removeEventListener("mouseenter", handleRootEnter);
|
|
100
|
+
root.removeEventListener("mouseleave", handleRootLeave);
|
|
101
|
+
trigger.removeEventListener("keydown", handleTriggerKeydown);
|
|
102
|
+
content.removeEventListener("keydown", handleContentKeydown);
|
|
103
|
+
content.removeEventListener("click", handleItemClick);
|
|
104
|
+
cancelClose();
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
const handleTriggerClick = (e) => {
|
|
108
|
+
e.stopPropagation();
|
|
109
|
+
toggle();
|
|
110
|
+
};
|
|
111
|
+
const handleTriggerKeydown = (e) => {
|
|
112
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
toggle();
|
|
115
|
+
}
|
|
116
|
+
if (e.key === "ArrowDown" && !isOpen()) {
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
openWithFocus();
|
|
119
|
+
}
|
|
120
|
+
if (e.key === "Escape") {
|
|
121
|
+
close();
|
|
122
|
+
trigger.focus();
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const handleContentKeydown = (e) => {
|
|
126
|
+
if (e.key === "Escape") {
|
|
127
|
+
close();
|
|
128
|
+
trigger.focus();
|
|
129
|
+
}
|
|
130
|
+
if (e.key === "Tab") {
|
|
131
|
+
close();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const handleOutsideClick = (e) => {
|
|
135
|
+
if (!root.contains(e.target)) close();
|
|
136
|
+
};
|
|
137
|
+
trigger.addEventListener("click", handleTriggerClick);
|
|
138
|
+
trigger.addEventListener("keydown", handleTriggerKeydown);
|
|
139
|
+
content.addEventListener("keydown", handleContentKeydown);
|
|
140
|
+
content.addEventListener("click", handleItemClick);
|
|
141
|
+
document.addEventListener("click", handleOutsideClick);
|
|
142
|
+
cleanupFn = () => {
|
|
143
|
+
trigger.removeEventListener("click", handleTriggerClick);
|
|
144
|
+
trigger.removeEventListener("keydown", handleTriggerKeydown);
|
|
145
|
+
content.removeEventListener("keydown", handleContentKeydown);
|
|
146
|
+
content.removeEventListener("click", handleItemClick);
|
|
147
|
+
document.removeEventListener("click", handleOutsideClick);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
initialized.set(root, cleanupFn);
|
|
151
|
+
const cleanupObserver = new MutationObserver(() => {
|
|
152
|
+
if (!document.contains(root)) {
|
|
153
|
+
cleanupFn();
|
|
154
|
+
cleanupObserver.disconnect();
|
|
155
|
+
initialized.delete(root);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
cleanupObserver.observe(document.body, {
|
|
159
|
+
childList: true,
|
|
160
|
+
subtree: true
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export { enhanceDropdown };
|
|
165
|
+
//# sourceMappingURL=dropdown.js.map
|
|
166
|
+
//# sourceMappingURL=dropdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/guards.ts","../../src/internal/dropdown.ts"],"names":[],"mappings":"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","file":"dropdown.js","sourcesContent":["export function canEnhance(): boolean {\n if (typeof window === \"undefined\") return false;\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n return false;\n }\n return true;\n}\n","// enhancers/dropdown.ts\nimport { canEnhance } from \"./guards\";\n\n// ✅ CRITERION 3: WeakMap prevents duplicate initialization across multiple instances\nconst initialized = new WeakMap<HTMLElement, CleanupFn>();\ntype CleanupFn = () => void;\n\nexport function enhanceDropdown(root: HTMLElement) {\n // ✅ CRITERION 6: Secondary reduced motion guard (primary is in .astro script block)\n if (!canEnhance()) return;\n\n // ✅ CRITERION 3: Prevent duplicate initialization\n if (initialized.has(root)) return;\n\n const trigger = root.querySelector<HTMLElement>(\"[data-dropdown-trigger]\");\n const content = root.querySelector<HTMLElement>(\"[data-dropdown-content]\");\n\n if (!trigger || !content) return;\n\n // Mark as JS-ready so CSS transitions activate\n root.dataset.ready = \"true\";\n\n // ─── Helpers ──────────────────────────────────────────────────────────────\n\n const isOpen = () => root.dataset.state === \"open\";\n const triggerMode = root.dataset.trigger ?? \"click\";\n const hoverDelay = parseInt(root.dataset.hoverDelay ?? \"120\", 10);\n\n const open = () => {\n root.dataset.state = \"open\";\n trigger.setAttribute(\"aria-expanded\", \"true\");\n content.setAttribute(\"aria-hidden\", \"false\");\n };\n\n const openWithFocus = () => {\n open();\n // Move focus into first focusable menu item (click/keyboard only)\n const firstItem = content.querySelector<HTMLElement>(\n \"button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])\",\n );\n firstItem?.focus();\n };\n\n const close = () => {\n root.dataset.state = \"closed\";\n trigger.setAttribute(\"aria-expanded\", \"false\");\n content.setAttribute(\"aria-hidden\", \"true\");\n };\n\n const toggle = () => (isOpen() ? close() : openWithFocus());\n\n // ─── Content interactions ─────────────────────────────────────────────────\n\n const handleItemClick = (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n const item = target.closest(\".menu-item\") as HTMLElement | null;\n if (item) {\n const indexStr = item.dataset.index;\n if (indexStr) {\n const event = new CustomEvent(\"dropdown-select\", {\n detail: { index: parseInt(indexStr, 10), item: item }\n });\n root.dispatchEvent(event);\n }\n close();\n }\n };\n\n // ─── Wire up ──────────────────────────────────────────────────────────────\n\n let cleanupFn: CleanupFn;\n\n if (triggerMode === \"hover\") {\n // ─── Hover mode ─────────────────────────────────────────────────────────\n let closeTimer: ReturnType<typeof setTimeout> | null = null;\n\n const cancelClose = () => {\n if (closeTimer !== null) {\n clearTimeout(closeTimer);\n closeTimer = null;\n }\n };\n\n const scheduleClose = () => {\n cancelClose();\n closeTimer = setTimeout(close, hoverDelay);\n };\n\n const handleRootEnter = () => {\n cancelClose();\n open();\n };\n const handleRootLeave = () => {\n scheduleClose();\n };\n\n // Keyboard still works for accessibility\n const handleTriggerKeydown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n close();\n trigger.focus();\n }\n if (\n (e.key === \"Enter\" || e.key === \" \" || e.key === \"ArrowDown\") &&\n !isOpen()\n ) {\n e.preventDefault();\n openWithFocus();\n }\n };\n\n const handleContentKeydown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n close();\n trigger.focus();\n }\n if (e.key === \"Tab\") {\n close();\n }\n };\n\n root.addEventListener(\"mouseenter\", handleRootEnter);\n root.addEventListener(\"mouseleave\", handleRootLeave);\n trigger.addEventListener(\"keydown\", handleTriggerKeydown);\n content.addEventListener(\"keydown\", handleContentKeydown);\n content.addEventListener(\"click\", handleItemClick);\n\n cleanupFn = () => {\n root.removeEventListener(\"mouseenter\", handleRootEnter);\n root.removeEventListener(\"mouseleave\", handleRootLeave);\n trigger.removeEventListener(\"keydown\", handleTriggerKeydown);\n content.removeEventListener(\"keydown\", handleContentKeydown);\n content.removeEventListener(\"click\", handleItemClick);\n cancelClose();\n };\n } else {\n // ─── Click mode (default) ────────────────────────────────────────────────\n const handleTriggerClick = (e: MouseEvent) => {\n e.stopPropagation();\n toggle();\n };\n\n const handleTriggerKeydown = (e: KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n toggle();\n }\n if (e.key === \"ArrowDown\" && !isOpen()) {\n e.preventDefault();\n openWithFocus();\n }\n if (e.key === \"Escape\") {\n close();\n trigger.focus();\n }\n };\n\n const handleContentKeydown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n close();\n trigger.focus();\n }\n if (e.key === \"Tab\") {\n close();\n }\n };\n\n const handleOutsideClick = (e: MouseEvent) => {\n if (!root.contains(e.target as Node)) close();\n };\n\n trigger.addEventListener(\"click\", handleTriggerClick);\n trigger.addEventListener(\"keydown\", handleTriggerKeydown);\n content.addEventListener(\"keydown\", handleContentKeydown);\n content.addEventListener(\"click\", handleItemClick);\n document.addEventListener(\"click\", handleOutsideClick);\n\n cleanupFn = () => {\n trigger.removeEventListener(\"click\", handleTriggerClick);\n trigger.removeEventListener(\"keydown\", handleTriggerKeydown);\n content.removeEventListener(\"keydown\", handleContentKeydown);\n content.removeEventListener(\"click\", handleItemClick);\n document.removeEventListener(\"click\", handleOutsideClick);\n };\n }\n\n // ─── Cleanup ──────────────────────────────────────────────────────────────\n\n // ✅ CRITERION 3: Cleanup function stored in WeakMap\n initialized.set(root, cleanupFn);\n\n // ✅ CRITERION 3: MutationObserver watches for DOM removal → guaranteed cleanup\n const cleanupObserver = new MutationObserver(() => {\n if (!document.contains(root)) {\n cleanupFn();\n cleanupObserver.disconnect();\n initialized.delete(root);\n }\n });\n\n cleanupObserver.observe(document.body, {\n childList: true,\n subtree: true,\n });\n}\n"]}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// src/internal/guards.ts
|
|
2
|
+
function canEnhance() {
|
|
3
|
+
if (typeof window === "undefined") return false;
|
|
4
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/internal/fadein.ts
|
|
11
|
+
var initialized = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
var selector = '[data-astro-fade-in][data-enhance="true"]:not([data-ready])';
|
|
13
|
+
var bootstrapObserver = null;
|
|
14
|
+
var managedRoots = 0;
|
|
15
|
+
var parseThreshold = (value) => {
|
|
16
|
+
const parsed = Number(value);
|
|
17
|
+
if (!Number.isFinite(parsed)) return 0.1;
|
|
18
|
+
return Math.min(Math.max(parsed, 0), 1);
|
|
19
|
+
};
|
|
20
|
+
var emitFadeInEvent = (root, state) => {
|
|
21
|
+
root.dispatchEvent(
|
|
22
|
+
new CustomEvent(`astro:fadein:${state}`, {
|
|
23
|
+
bubbles: true,
|
|
24
|
+
detail: {
|
|
25
|
+
once: root.dataset.once === "true",
|
|
26
|
+
enhance: root.dataset.enhance === "true"
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
var initElement = (element) => {
|
|
32
|
+
if (!(element instanceof HTMLElement)) return;
|
|
33
|
+
if (element.dataset.ready === "true") return;
|
|
34
|
+
element.dataset.ready = "true";
|
|
35
|
+
enhanceFadeIn(element);
|
|
36
|
+
};
|
|
37
|
+
var initFadeIns = (root = document) => {
|
|
38
|
+
root.querySelectorAll(selector).forEach(initElement);
|
|
39
|
+
};
|
|
40
|
+
var ensureBootstrapObserver = () => {
|
|
41
|
+
if (bootstrapObserver || typeof document === "undefined") return;
|
|
42
|
+
bootstrapObserver = new MutationObserver((mutations) => {
|
|
43
|
+
mutations.forEach((mutation) => {
|
|
44
|
+
mutation.addedNodes.forEach((node) => {
|
|
45
|
+
if (!(node instanceof Element)) return;
|
|
46
|
+
if (node.matches(selector)) initElement(node);
|
|
47
|
+
initFadeIns(node);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
bootstrapObserver.observe(document.body, {
|
|
52
|
+
childList: true,
|
|
53
|
+
subtree: true
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
var maybeDisconnectBootstrapObserver = () => {
|
|
57
|
+
if (managedRoots > 0 || !bootstrapObserver) return;
|
|
58
|
+
bootstrapObserver.disconnect();
|
|
59
|
+
bootstrapObserver = null;
|
|
60
|
+
};
|
|
61
|
+
function bootFadeIn(root = document) {
|
|
62
|
+
if (!canEnhance()) return;
|
|
63
|
+
ensureBootstrapObserver();
|
|
64
|
+
initFadeIns(root);
|
|
65
|
+
}
|
|
66
|
+
function enhanceFadeIn(root) {
|
|
67
|
+
if (!canEnhance()) return;
|
|
68
|
+
if (initialized.has(root)) return;
|
|
69
|
+
root.dataset.state = "hidden";
|
|
70
|
+
managedRoots += 1;
|
|
71
|
+
emitFadeInEvent(root, "init");
|
|
72
|
+
const isOnce = root.dataset.once === "true";
|
|
73
|
+
const threshold = parseThreshold(root.dataset.threshold);
|
|
74
|
+
const rootMargin = root.dataset.rootMargin?.trim() || "0px 0px -50px 0px";
|
|
75
|
+
let isCleanedUp = false;
|
|
76
|
+
const cleanup = () => {
|
|
77
|
+
if (isCleanedUp) return;
|
|
78
|
+
isCleanedUp = true;
|
|
79
|
+
observer.disconnect();
|
|
80
|
+
cleanupObserver?.disconnect();
|
|
81
|
+
initialized.delete(root);
|
|
82
|
+
managedRoots = Math.max(0, managedRoots - 1);
|
|
83
|
+
maybeDisconnectBootstrapObserver();
|
|
84
|
+
};
|
|
85
|
+
const observer = new IntersectionObserver(
|
|
86
|
+
(entries) => {
|
|
87
|
+
entries.forEach((entry) => {
|
|
88
|
+
if (entry.isIntersecting) {
|
|
89
|
+
root.dataset.state = "visible";
|
|
90
|
+
emitFadeInEvent(root, "visible");
|
|
91
|
+
if (isOnce) cleanup();
|
|
92
|
+
} else if (!isOnce) {
|
|
93
|
+
root.dataset.state = "hidden";
|
|
94
|
+
emitFadeInEvent(root, "hidden");
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
threshold,
|
|
100
|
+
rootMargin
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
observer.observe(root);
|
|
104
|
+
initialized.set(root, cleanup);
|
|
105
|
+
const cleanupObserver = new MutationObserver(() => {
|
|
106
|
+
if (!document.contains(root)) cleanup();
|
|
107
|
+
});
|
|
108
|
+
cleanupObserver.observe(document.body, {
|
|
109
|
+
childList: true,
|
|
110
|
+
subtree: true
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { bootFadeIn, enhanceFadeIn };
|
|
115
|
+
//# sourceMappingURL=fadein.js.map
|
|
116
|
+
//# sourceMappingURL=fadein.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/guards.ts","../../src/internal/fadein.ts"],"names":[],"mappings":"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","file":"fadein.js","sourcesContent":["export function canEnhance(): boolean {\n if (typeof window === \"undefined\") return false;\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n return false;\n }\n return true;\n}\n","import { canEnhance } from \"./guards\";\n\ntype CleanupFn = () => void;\n\nconst initialized = new WeakMap<HTMLElement, CleanupFn>();\nconst selector = '[data-astro-fade-in][data-enhance=\"true\"]:not([data-ready])';\n\nlet bootstrapObserver: MutationObserver | null = null;\nlet managedRoots = 0;\n\nconst parseThreshold = (value: string | undefined) => {\n const parsed = Number(value);\n if (!Number.isFinite(parsed)) return 0.1;\n return Math.min(Math.max(parsed, 0), 1);\n};\n\nconst emitFadeInEvent = (\n root: HTMLElement,\n state: \"init\" | \"visible\" | \"hidden\",\n) => {\n root.dispatchEvent(\n new CustomEvent(`astro:fadein:${state}`, {\n bubbles: true,\n detail: {\n once: root.dataset.once === \"true\",\n enhance: root.dataset.enhance === \"true\",\n },\n }),\n );\n};\n\nconst initElement = (element: Element) => {\n if (!(element instanceof HTMLElement)) return;\n if (element.dataset.ready === \"true\") return;\n element.dataset.ready = \"true\";\n enhanceFadeIn(element);\n};\n\nconst initFadeIns = (root: ParentNode = document) => {\n root.querySelectorAll(selector).forEach(initElement);\n};\n\nconst ensureBootstrapObserver = () => {\n if (bootstrapObserver || typeof document === \"undefined\") return;\n\n bootstrapObserver = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n mutation.addedNodes.forEach((node) => {\n if (!(node instanceof Element)) return;\n if (node.matches(selector)) initElement(node);\n initFadeIns(node);\n });\n });\n });\n\n bootstrapObserver.observe(document.body, {\n childList: true,\n subtree: true,\n });\n};\n\nconst maybeDisconnectBootstrapObserver = () => {\n if (managedRoots > 0 || !bootstrapObserver) return;\n bootstrapObserver.disconnect();\n bootstrapObserver = null;\n};\n\nexport function bootFadeIn(root: ParentNode = document) {\n if (!canEnhance()) return;\n ensureBootstrapObserver();\n initFadeIns(root);\n}\n\nexport function enhanceFadeIn(root: HTMLElement) {\n if (!canEnhance()) return;\n if (initialized.has(root)) return;\n\n root.dataset.state = \"hidden\";\n managedRoots += 1;\n emitFadeInEvent(root, \"init\");\n\n const isOnce = root.dataset.once === \"true\";\n const threshold = parseThreshold(root.dataset.threshold);\n const rootMargin = root.dataset.rootMargin?.trim() || \"0px 0px -50px 0px\";\n let isCleanedUp = false;\n\n const cleanup = () => {\n if (isCleanedUp) return;\n isCleanedUp = true;\n observer.disconnect();\n cleanupObserver?.disconnect();\n initialized.delete(root);\n managedRoots = Math.max(0, managedRoots - 1);\n maybeDisconnectBootstrapObserver();\n };\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n root.dataset.state = \"visible\";\n emitFadeInEvent(root, \"visible\");\n if (isOnce) cleanup();\n } else if (!isOnce) {\n root.dataset.state = \"hidden\";\n emitFadeInEvent(root, \"hidden\");\n }\n });\n },\n {\n threshold,\n rootMargin,\n },\n );\n\n observer.observe(root);\n initialized.set(root, cleanup);\n\n const cleanupObserver = new MutationObserver(() => {\n if (!document.contains(root)) cleanup();\n });\n\n cleanupObserver.observe(document.body, {\n childList: true,\n subtree: true,\n });\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/internal/guards.ts
|
|
2
|
+
function canEnhance() {
|
|
3
|
+
if (typeof window === "undefined") return false;
|
|
4
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { canEnhance };
|
|
11
|
+
//# sourceMappingURL=guards.js.map
|
|
12
|
+
//# sourceMappingURL=guards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/guards.ts"],"names":[],"mappings":";AAAO,SAAS,UAAA,GAAsB;AACpC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,IAAI,MAAA,CAAO,UAAA,CAAW,kCAAkC,CAAA,CAAE,OAAA,EAAS;AACjE,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,IAAA;AACT","file":"guards.js","sourcesContent":["export function canEnhance(): boolean {\n if (typeof window === \"undefined\") return false;\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n return false;\n }\n return true;\n}\n"]}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/internal/guards.ts
|
|
2
|
+
function canEnhance() {
|
|
3
|
+
if (typeof window === "undefined") return false;
|
|
4
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/internal/tabs.ts
|
|
11
|
+
var initialized = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
function clamp(n, min, max) {
|
|
13
|
+
return Math.max(min, Math.min(max, n));
|
|
14
|
+
}
|
|
15
|
+
function enhanceAnimatedTabs(root) {
|
|
16
|
+
if (typeof window === "undefined") return;
|
|
17
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
18
|
+
if (!canEnhance()) return;
|
|
19
|
+
if (initialized.has(root)) return initialized.get(root);
|
|
20
|
+
const tablist = root.querySelector("[data-tablist]");
|
|
21
|
+
const indicator = root.querySelector(
|
|
22
|
+
"[data-indicator]"
|
|
23
|
+
);
|
|
24
|
+
const tabs = Array.from(
|
|
25
|
+
root.querySelectorAll("[data-tab]")
|
|
26
|
+
);
|
|
27
|
+
const panels = Array.from(
|
|
28
|
+
root.querySelectorAll("[data-panel]")
|
|
29
|
+
);
|
|
30
|
+
if (!tablist || !indicator) return;
|
|
31
|
+
if (tabs.length === 0 || panels.length === 0) return;
|
|
32
|
+
const defaultTab = root.dataset.defaultTab;
|
|
33
|
+
const findIndexById = (id) => {
|
|
34
|
+
if (!id) return -1;
|
|
35
|
+
return tabs.findIndex((t) => t.dataset.tabId === id);
|
|
36
|
+
};
|
|
37
|
+
let activeIndex = clamp(findIndexById(defaultTab), 0, tabs.length - 1);
|
|
38
|
+
const setIndicatorTo = (tabEl) => {
|
|
39
|
+
const listRect = tablist.getBoundingClientRect();
|
|
40
|
+
const tabRect = tabEl.getBoundingClientRect();
|
|
41
|
+
const x = tabRect.left - listRect.left + tablist.scrollLeft;
|
|
42
|
+
const w = tabRect.width;
|
|
43
|
+
root.setAttribute("data-indicator-x", String(x));
|
|
44
|
+
root.setAttribute("data-indicator-w", String(w));
|
|
45
|
+
};
|
|
46
|
+
const setActive = (nextIndex, opts) => {
|
|
47
|
+
const idx = clamp(nextIndex, 0, tabs.length - 1);
|
|
48
|
+
activeIndex = idx;
|
|
49
|
+
tabs.forEach((t, i) => {
|
|
50
|
+
const isActive = i === idx;
|
|
51
|
+
t.dataset.active = isActive ? "true" : "false";
|
|
52
|
+
t.setAttribute("aria-selected", isActive ? "true" : "false");
|
|
53
|
+
t.tabIndex = isActive ? 0 : -1;
|
|
54
|
+
});
|
|
55
|
+
const activeTab = tabs[idx];
|
|
56
|
+
panels.forEach((p) => {
|
|
57
|
+
const isActive = activeTab && p.dataset.panelId === activeTab.dataset.tabId;
|
|
58
|
+
if (isActive) {
|
|
59
|
+
p.dataset.active = "true";
|
|
60
|
+
p.removeAttribute("hidden");
|
|
61
|
+
p.setAttribute("aria-hidden", "false");
|
|
62
|
+
} else {
|
|
63
|
+
p.dataset.active = "false";
|
|
64
|
+
p.setAttribute("hidden", "");
|
|
65
|
+
p.setAttribute("aria-hidden", "true");
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
if (activeTab) {
|
|
69
|
+
setIndicatorTo(activeTab);
|
|
70
|
+
if (opts?.focus) activeTab.focus();
|
|
71
|
+
root.dispatchEvent(new CustomEvent("astroanimate:tabs:change", {
|
|
72
|
+
detail: { index: idx, id: activeTab.dataset.tabId }
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const onClick = (e) => {
|
|
77
|
+
const target = e.target;
|
|
78
|
+
const btn = target?.closest("button[data-tab]");
|
|
79
|
+
if (!btn) return;
|
|
80
|
+
const idx = tabs.indexOf(btn);
|
|
81
|
+
if (idx === -1) return;
|
|
82
|
+
setActive(idx, { focus: true });
|
|
83
|
+
};
|
|
84
|
+
const onKeyDown = (e) => {
|
|
85
|
+
const key = e.key;
|
|
86
|
+
if (key === "ArrowRight" || key === "ArrowDown") {
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
setActive((activeIndex + 1) % tabs.length, { focus: true });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (key === "ArrowLeft" || key === "ArrowUp") {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
setActive((activeIndex - 1 + tabs.length) % tabs.length, { focus: true });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (key === "Home") {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
setActive(0, { focus: true });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (key === "End") {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
setActive(tabs.length - 1, { focus: true });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const onResize = () => {
|
|
108
|
+
const active = tabs[activeIndex];
|
|
109
|
+
if (active) setIndicatorTo(active);
|
|
110
|
+
};
|
|
111
|
+
tablist.addEventListener("click", onClick);
|
|
112
|
+
tablist.addEventListener("keydown", onKeyDown);
|
|
113
|
+
window.addEventListener("resize", onResize);
|
|
114
|
+
root.dataset.enhanced = "true";
|
|
115
|
+
root.dataset.ready = "true";
|
|
116
|
+
setActive(activeIndex);
|
|
117
|
+
requestAnimationFrame(() => {
|
|
118
|
+
const active = tabs[activeIndex];
|
|
119
|
+
if (active) setIndicatorTo(active);
|
|
120
|
+
});
|
|
121
|
+
const destroy = () => {
|
|
122
|
+
tablist.removeEventListener("click", onClick);
|
|
123
|
+
tablist.removeEventListener("keydown", onKeyDown);
|
|
124
|
+
window.removeEventListener("resize", onResize);
|
|
125
|
+
initialized.delete(root);
|
|
126
|
+
};
|
|
127
|
+
initialized.set(root, destroy);
|
|
128
|
+
const cleanupObserver = new MutationObserver(() => {
|
|
129
|
+
if (!document.contains(root)) {
|
|
130
|
+
destroy();
|
|
131
|
+
cleanupObserver.disconnect();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
cleanupObserver.observe(document.body, { childList: true, subtree: true });
|
|
135
|
+
return destroy;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { enhanceAnimatedTabs };
|
|
139
|
+
//# sourceMappingURL=tabs.js.map
|
|
140
|
+
//# sourceMappingURL=tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/guards.ts","../../src/internal/tabs.ts"],"names":[],"mappings":"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","file":"tabs.js","sourcesContent":["export function canEnhance(): boolean {\n if (typeof window === \"undefined\") return false;\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n return false;\n }\n return true;\n}\n","import { canEnhance } from \"./guards\";\n\nconst initialized = new WeakMap<HTMLElement, () => void>();\n\nfunction clamp(n: number, min: number, max: number) {\n return Math.max(min, Math.min(max, n));\n}\n\nexport function enhanceAnimatedTabs(\n root: HTMLElement,\n): (() => void) | undefined {\n if (typeof window === \"undefined\") return;\n \n // Criterion 6 — Reduced Motion\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n if (!canEnhance()) return;\n if (initialized.has(root)) return initialized.get(root);\n\n const tablist = root.querySelector(\"[data-tablist]\") as HTMLElement | null;\n const indicator = root.querySelector(\n \"[data-indicator]\",\n ) as HTMLElement | null;\n\n const tabs = Array.from(\n root.querySelectorAll(\"[data-tab]\"),\n ) as HTMLButtonElement[];\n const panels = Array.from(\n root.querySelectorAll(\"[data-panel]\"),\n ) as HTMLElement[];\n\n if (!tablist || !indicator) return;\n if (tabs.length === 0 || panels.length === 0) return;\n\n const defaultTab = root.dataset.defaultTab;\n\n const findIndexById = (id: string | undefined | null) => {\n if (!id) return -1;\n return tabs.findIndex((t) => t.dataset.tabId === id);\n };\n\n let activeIndex = clamp(findIndexById(defaultTab), 0, tabs.length - 1);\n\n const setIndicatorTo = (tabEl: HTMLElement) => {\n const listRect = tablist.getBoundingClientRect();\n const tabRect = tabEl.getBoundingClientRect();\n\n const x = tabRect.left - listRect.left + tablist.scrollLeft;\n const w = tabRect.width;\n\n // Criterion 7 — CSS-First Architecture\n root.setAttribute(\"data-indicator-x\", String(x));\n root.setAttribute(\"data-indicator-w\", String(w));\n };\n\n const setActive = (nextIndex: number, opts?: { focus?: boolean }) => {\n const idx = clamp(nextIndex, 0, tabs.length - 1);\n activeIndex = idx;\n\n tabs.forEach((t, i) => {\n const isActive = i === idx;\n t.dataset.active = isActive ? \"true\" : \"false\";\n t.setAttribute(\"aria-selected\", isActive ? \"true\" : \"false\");\n t.tabIndex = isActive ? 0 : -1;\n });\n\n const activeTab = tabs[idx];\n panels.forEach((p) => {\n const isActive = activeTab && p.dataset.panelId === activeTab.dataset.tabId;\n if (isActive) {\n p.dataset.active = \"true\";\n p.removeAttribute(\"hidden\");\n p.setAttribute(\"aria-hidden\", \"false\");\n } else {\n p.dataset.active = \"false\";\n p.setAttribute(\"hidden\", \"\");\n p.setAttribute(\"aria-hidden\", \"true\");\n }\n });\n\n if (activeTab) {\n setIndicatorTo(activeTab);\n if (opts?.focus) activeTab.focus();\n\n // Emit event for extensibility\n root.dispatchEvent(new CustomEvent(\"astroanimate:tabs:change\", {\n detail: { index: idx, id: activeTab.dataset.tabId }\n }));\n }\n };\n\n const onClick = (e: Event) => {\n const target = e.target as HTMLElement | null;\n const btn = target?.closest(\"button[data-tab]\") as HTMLButtonElement | null;\n if (!btn) return;\n\n const idx = tabs.indexOf(btn);\n if (idx === -1) return;\n\n setActive(idx, { focus: true });\n };\n\n const onKeyDown = (e: KeyboardEvent) => {\n const key = e.key;\n\n if (key === \"ArrowRight\" || key === \"ArrowDown\") {\n e.preventDefault();\n setActive((activeIndex + 1) % tabs.length, { focus: true });\n return;\n }\n\n if (key === \"ArrowLeft\" || key === \"ArrowUp\") {\n e.preventDefault();\n setActive((activeIndex - 1 + tabs.length) % tabs.length, { focus: true });\n return;\n }\n\n if (key === \"Home\") {\n e.preventDefault();\n setActive(0, { focus: true });\n return;\n }\n\n if (key === \"End\") {\n e.preventDefault();\n setActive(tabs.length - 1, { focus: true });\n return;\n }\n };\n\n const onResize = () => {\n const active = tabs[activeIndex];\n if (active) setIndicatorTo(active);\n };\n\n tablist.addEventListener(\"click\", onClick);\n tablist.addEventListener(\"keydown\", onKeyDown);\n window.addEventListener(\"resize\", onResize);\n\n root.dataset.enhanced = \"true\";\n root.dataset.ready = \"true\";\n\n setActive(activeIndex);\n \n // Initial position might need a frame to settle\n requestAnimationFrame(() => {\n const active = tabs[activeIndex];\n if (active) setIndicatorTo(active);\n });\n\n const destroy = () => {\n tablist.removeEventListener(\"click\", onClick);\n tablist.removeEventListener(\"keydown\", onKeyDown);\n window.removeEventListener(\"resize\", onResize);\n initialized.delete(root);\n };\n\n initialized.set(root, destroy);\n\n const cleanupObserver = new MutationObserver(() => {\n if (!document.contains(root)) {\n destroy();\n cleanupObserver.disconnect();\n }\n });\n cleanupObserver.observe(document.body, { childList: true, subtree: true });\n\n return destroy;\n}\n"]}
|