@hortonstudio/main 1.8.0 → 1.8.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.
@@ -593,10 +593,18 @@ export function init() {
593
593
  }
594
594
 
595
595
 
596
- const template = tocList.children[0];
596
+ const template = tocList.children[0].cloneNode(true);
597
+ // Remove is-active class from template if it exists
598
+ const templateLink = template.querySelector("a");
599
+ if (templateLink) {
600
+ templateLink.classList.remove('is-active');
601
+ }
602
+
603
+ // Clear all original TOC items
597
604
  tocList.innerHTML = "";
605
+
598
606
  const h2Headings = contentArea.querySelectorAll("h2");
599
-
607
+
600
608
 
601
609
  // Create sections and wrap content
602
610
  h2Headings.forEach((heading) => {
@@ -604,7 +612,7 @@ export function init() {
604
612
  .toLowerCase()
605
613
  .replace(/[^a-z0-9]+/g, "-")
606
614
  .replace(/(^-|-$)/g, "");
607
-
615
+
608
616
  const section = document.createElement("div");
609
617
  section.id = sectionId;
610
618
  heading.parentNode.insertBefore(section, heading);
@@ -661,78 +669,64 @@ export function init() {
661
669
  tocList.appendChild(tocItem);
662
670
  });
663
671
 
664
- // Set up IntersectionObserver for active state
672
+ // Set up IntersectionObserver for active state (Webflow-style)
665
673
  const sections = Array.from(h2Headings).map(heading => heading.parentElement);
666
674
  const tocLinks = tocList.querySelectorAll('a');
667
- let currentActiveId = null;
675
+ let currentActive = null;
668
676
 
669
- const observerOptions = {
670
- rootMargin: '-10% 0px -50% 0px',
671
- threshold: [0, 0.25, 0.5, 0.75, 1]
672
- };
677
+ const updateActiveLink = () => {
678
+ const windowHeight = window.innerHeight;
679
+ const trigger = windowHeight * 0.75; // 25% from bottom
673
680
 
674
- const observer = new IntersectionObserver((entries) => {
675
- // Collect all currently intersecting sections
676
- const intersectingSections = [];
677
-
678
- entries.forEach(entry => {
679
- const sectionId = entry.target.id;
680
- const existingIndex = intersectingSections.findIndex(s => s.id === sectionId);
681
-
682
- if (entry.isIntersecting) {
683
- if (existingIndex === -1) {
684
- intersectingSections.push({
685
- id: sectionId,
686
- ratio: entry.intersectionRatio,
687
- element: entry.target
688
- });
689
- } else {
690
- intersectingSections[existingIndex].ratio = entry.intersectionRatio;
691
- }
692
- }
693
- });
681
+ let newActive = null;
694
682
 
695
- // Find all currently intersecting sections (not just from this batch)
696
- const allIntersecting = sections
697
- .filter(section => {
698
- const rect = section.getBoundingClientRect();
699
- const windowHeight = window.innerHeight;
700
- // Check if section is in the active zone
701
- return rect.top < windowHeight * 0.6 && rect.bottom > windowHeight * 0.1;
702
- })
703
- .map(section => ({
704
- id: section.id,
705
- top: section.getBoundingClientRect().top,
706
- element: section
707
- }));
708
-
709
- if (allIntersecting.length === 0) return;
710
-
711
- // Pick the topmost section in the active zone
712
- const topmost = allIntersecting.reduce((prev, curr) =>
713
- curr.top < prev.top ? curr : prev
714
- );
683
+ // Find the last section whose top is above the trigger point
684
+ for (let i = sections.length - 1; i >= 0; i--) {
685
+ const rect = sections[i].getBoundingClientRect();
686
+ if (rect.top <= trigger) {
687
+ newActive = sections[i].id;
688
+ break;
689
+ }
690
+ }
715
691
 
716
- // Only update if the active section changed
717
- if (currentActiveId !== topmost.id) {
718
- currentActiveId = topmost.id;
692
+ // Only update if active section changed
693
+ if (newActive !== currentActive) {
694
+ currentActive = newActive;
719
695
 
720
- // Remove is-active from all links
696
+ // Remove all is-active
721
697
  tocLinks.forEach(link => link.classList.remove('is-active'));
722
698
 
723
- // Add is-active to the corresponding link
724
- const activeLink = Array.from(tocLinks).find(link =>
725
- link.getAttribute('href') === `#${topmost.id}`
726
- );
727
- if (activeLink) {
728
- activeLink.classList.add('is-active');
699
+ // Add to current
700
+ if (currentActive) {
701
+ const activeLink = Array.from(tocLinks).find(link =>
702
+ link.getAttribute('href') === `#${currentActive}`
703
+ );
704
+ if (activeLink) {
705
+ activeLink.classList.add('is-active');
706
+ }
729
707
  }
730
708
  }
709
+ };
710
+
711
+ const observerOptions = {
712
+ rootMargin: '0px 0px -75% 0px',
713
+ threshold: 0
714
+ };
715
+
716
+ const observer = new IntersectionObserver(() => {
717
+ updateActiveLink();
731
718
  }, observerOptions);
732
719
 
733
720
  // Observe all sections
734
721
  sections.forEach(section => observer.observe(section));
735
722
 
723
+ // Also update on scroll for smoother tracking
724
+ let scrollTimeout;
725
+ window.addEventListener('scroll', () => {
726
+ if (scrollTimeout) clearTimeout(scrollTimeout);
727
+ scrollTimeout = setTimeout(updateActiveLink, 50);
728
+ });
729
+
736
730
  });
737
731
  }
738
732
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hortonstudio/main",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "Animation and utility library for client websites",
5
5
  "main": "index.js",
6
6
  "type": "module",