@brandocms/jupiter 3.54.4 → 4.0.0-beta.1

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.
Files changed (67) hide show
  1. package/README.md +318 -52
  2. package/package.json +26 -16
  3. package/src/index.js +5 -0
  4. package/src/modules/Application/index.js +37 -5
  5. package/src/modules/Breakpoints/index.js +116 -36
  6. package/src/modules/Cookies/index.js +67 -15
  7. package/src/modules/CoverOverlay/index.js +2 -2
  8. package/src/modules/Dom/index.js +6 -0
  9. package/src/modules/Dropdown/index.js +15 -6
  10. package/src/modules/EqualHeightElements/index.js +8 -6
  11. package/src/modules/EqualHeightImages/index.js +9 -6
  12. package/src/modules/FixedHeader/index.js +57 -1
  13. package/src/modules/FooterReveal/index.js +3 -3
  14. package/src/modules/HeroSlider/index.js +39 -30
  15. package/src/modules/HeroVideo/index.js +64 -24
  16. package/src/modules/Lazyload/index.js +27 -0
  17. package/src/modules/Lightbox/index.js +90 -31
  18. package/src/modules/Links/index.js +23 -2
  19. package/src/modules/MobileMenu/index.js +50 -21
  20. package/src/modules/Moonwalk/index.js +131 -4
  21. package/src/modules/Parallax/index.js +280 -57
  22. package/src/modules/Popover/index.js +28 -16
  23. package/src/modules/Popup/index.js +155 -29
  24. package/src/modules/ScrollSpy/index.js +21 -0
  25. package/src/modules/StackedBoxes/index.js +6 -4
  26. package/src/modules/StickyHeader/index.js +45 -24
  27. package/src/modules/Toggler/index.js +44 -5
  28. package/src/modules/Typography/index.js +33 -20
  29. package/types/README.md +159 -0
  30. package/types/events/index.d.ts +20 -0
  31. package/types/index.d.ts +35 -0
  32. package/types/modules/Application/index.d.ts +168 -0
  33. package/types/modules/Breakpoints/index.d.ts +38 -0
  34. package/types/modules/Cookies/index.d.ts +81 -0
  35. package/types/modules/CoverOverlay/index.d.ts +6 -0
  36. package/types/modules/Dataloader/index.d.ts +35 -0
  37. package/types/modules/Dom/index.d.ts +40 -0
  38. package/types/modules/Dropdown/index.d.ts +38 -0
  39. package/types/modules/EqualHeightElements/index.d.ts +8 -0
  40. package/types/modules/EqualHeightImages/index.d.ts +11 -0
  41. package/types/modules/FeatureTests/index.d.ts +27 -0
  42. package/types/modules/FixedHeader/index.d.ts +219 -0
  43. package/types/modules/Fontloader/index.d.ts +5 -0
  44. package/types/modules/FooterReveal/index.d.ts +5 -0
  45. package/types/modules/HeroSlider/index.d.ts +28 -0
  46. package/types/modules/HeroVideo/index.d.ts +83 -0
  47. package/types/modules/Lazyload/index.d.ts +80 -0
  48. package/types/modules/Lightbox/index.d.ts +128 -0
  49. package/types/modules/Links/index.d.ts +55 -0
  50. package/types/modules/Marquee/index.d.ts +23 -0
  51. package/types/modules/MobileMenu/index.d.ts +63 -0
  52. package/types/modules/Moonwalk/index.d.ts +331 -0
  53. package/types/modules/Parallax/index.d.ts +93 -0
  54. package/types/modules/Popover/index.d.ts +17 -0
  55. package/types/modules/Popup/index.d.ts +89 -0
  56. package/types/modules/ScrollSpy/index.d.ts +29 -0
  57. package/types/modules/StackedBoxes/index.d.ts +9 -0
  58. package/types/modules/StickyHeader/index.d.ts +63 -0
  59. package/types/modules/Toggler/index.d.ts +26 -0
  60. package/types/modules/Typography/index.d.ts +77 -0
  61. package/types/utils/dispatchElementEvent.d.ts +1 -0
  62. package/types/utils/imageIsLoaded.d.ts +1 -0
  63. package/types/utils/imagesAreLoaded.d.ts +1 -0
  64. package/types/utils/loadScript.d.ts +2 -0
  65. package/types/utils/prefersReducedMotion.d.ts +4 -0
  66. package/types/utils/rafCallback.d.ts +2 -0
  67. package/types/utils/zoom.d.ts +4 -0
package/README.md CHANGED
@@ -532,36 +532,161 @@ Paragraph one and two will then get a `data-moonwalk="slide"` attribute.
532
532
 
533
533
  ## Popup
534
534
 
535
+ The Popup module allows you to create modal dialogs that appear when triggered by a button click.
536
+
535
537
  ### Options
536
538
 
537
- - `tweenIn: (el, popup) => {}`
538
- - Function that gets called to tween popup + background in.
539
- - `el` is the popup element itself, while `popup` is the `Popup()` class.
539
+ - `selector` - default `[data-popup]`
540
+ - CSS selector to find popup elements
541
+
542
+ - `responsive: (app) => boolean` - default `() => true`
543
+ - Function to determine if popup should be shown on current breakpoint
544
+
545
+ - `onOpen: (trigger, target, popup) => {}`
546
+ - Function called when popup opens
547
+ - `trigger` is the element that triggered the popup
548
+ - `target` is the popup element
549
+ - `popup` is the Popup instance
550
+
551
+ - `onClose: (popup) => {}`
552
+ - Function called when popup closes
553
+ - `popup` is the Popup instance
554
+
555
+ - `tweenIn: (trigger, target, popup) => {}`
556
+ - Function for animating the popup opening
557
+ - `trigger` is the element that triggered the popup
558
+ - `target` is the popup element
559
+ - `popup` is the Popup instance
540
560
  - Backdrop can be accessed as `popup.backdrop`
541
561
 
542
562
  - `tweenOut: (popup) => {}`
543
- - Function that gets called to tween popup + background out
563
+ - Function for animating the popup closing
564
+ - `popup` is the Popup instance
565
+ - Popup element can be accessed as `popup.currentPopup`
544
566
  - Backdrop can be accessed as `popup.backdrop`
545
567
 
546
- - `onClose: (popup) => {}`
547
- - Function that gets called right before `popup.close`
568
+ ### Basic Usage
569
+
570
+ HTML:
571
+
572
+ ```html
573
+ <div id="my-popup" data-popup>
574
+ <div class="popup-header">
575
+ <h3>My Popup</h3>
576
+ <button class="close-button" data-popup-close>×</button>
577
+ </div>
578
+ <div class="popup-content">
579
+ <p>This is a popup that appears when the trigger button is clicked.</p>
580
+ </div>
581
+ </div>
582
+
583
+ <button data-popup-trigger="#my-popup">Open Popup</button>
584
+ ```
585
+
586
+ JavaScript:
548
587
 
549
- ### Example
588
+ ```js
589
+ import { Application, Popup } from '@brandocms/jupiter'
590
+
591
+ const app = new Application()
592
+ const popup = new Popup(app)
593
+ ```
550
594
 
551
- Example HTML
595
+ ### Advanced Usage with Multiple Popups
596
+
597
+ You can create multiple independent popups by using the key-based system. This ensures that each popup operates independently, with its own backdrop and close behavior.
598
+
599
+ HTML:
552
600
 
553
601
  ```html
554
- <div class="newsletter-popup" data-popup>
555
- ...
556
- <button data-popup-close>
602
+ <!-- First popup with key "newsletter" -->
603
+ <div id="newsletter-popup" data-popup data-popup-key="newsletter">
604
+ <div class="popup-header">
605
+ <h3>Newsletter Signup</h3>
606
+ <button class="close-button" data-popup-close>×</button>
607
+ </div>
608
+ <div class="popup-content">
609
+ <p>Sign up for our newsletter!</p>
610
+ </div>
611
+ </div>
612
+
613
+ <!-- Second popup with key "login" -->
614
+ <div id="login-popup" data-popup data-popup-key="login">
615
+ <div class="popup-header">
616
+ <h3>Login</h3>
617
+ <button class="close-button" data-popup-close>×</button>
618
+ </div>
619
+ <div class="popup-content">
620
+ <p>Enter your credentials to log in.</p>
621
+ </div>
557
622
  </div>
558
623
 
559
- <button data-popup-trigger=".newsletter-popup">
560
- Open popup
624
+ <!-- Triggers for each popup with corresponding keys -->
625
+ <button data-popup-trigger="#newsletter-popup" data-popup-key="newsletter">
626
+ Subscribe to Newsletter
561
627
  </button>
628
+
629
+ <button data-popup-trigger="#login-popup" data-popup-key="login">
630
+ Login
631
+ </button>
632
+ ```
633
+
634
+ JavaScript:
635
+
636
+ ```js
637
+ import { Application, Popup } from '@brandocms/jupiter'
638
+
639
+ const app = new Application()
640
+
641
+ // Create separate instances for different types of popups
642
+ const newsletterPopup = new Popup(app, '[data-popup][data-popup-key="newsletter"]', {
643
+ onOpen: (trigger, target, popup) => {
644
+ console.log('Newsletter popup opened')
645
+ }
646
+ })
647
+
648
+ const loginPopup = new Popup(app, '[data-popup][data-popup-key="login"]', {
649
+ onOpen: (trigger, target, popup) => {
650
+ console.log('Login popup opened')
651
+ }
652
+ })
653
+ ```
654
+
655
+ ### Custom Animations
656
+
657
+ You can customize the animation for specific popups:
658
+
659
+ ```js
660
+ const customPopup = new Popup(app, '[data-popup][data-popup-key="custom"]', {
661
+ tweenIn: (trigger, target, popup) => {
662
+ // Set backdrop visible
663
+ gsap.set(popup.backdrop, { display: 'block' })
664
+ gsap.to(popup.backdrop, {
665
+ duration: 0.3,
666
+ opacity: 1,
667
+ onComplete: () => {
668
+ // Bounce in animation for popup
669
+ gsap.fromTo(
670
+ target,
671
+ {
672
+ scale: 0.5,
673
+ opacity: 0,
674
+ display: 'block'
675
+ },
676
+ {
677
+ duration: 0.5,
678
+ scale: 1,
679
+ opacity: 1,
680
+ ease: 'back.out(1.7)'
681
+ }
682
+ )
683
+ }
684
+ })
685
+ }
686
+ })
562
687
  ```
563
688
 
564
- Example CSS (PCSS)
689
+ ### CSS Styling
565
690
 
566
691
  ```scss
567
692
  [data-popup] {
@@ -576,9 +701,12 @@ Example CSS (PCSS)
576
701
  text-align: center;
577
702
  display: none;
578
703
  opacity: 0;
579
-
704
+ transform: translate(-50%, -50%);
705
+ border-radius: 6px;
706
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
707
+
580
708
  @responsive mobile {
581
- width: 80%;
709
+ width: 90%;
582
710
  }
583
711
  }
584
712
 
@@ -586,13 +714,23 @@ Example CSS (PCSS)
586
714
  z-index: 4999;
587
715
  display: none;
588
716
  opacity: 0;
589
- background-color: theme(colors.blue.100);
717
+ background-color: rgba(0, 0, 0, 0.5);
590
718
  position: fixed;
591
719
  top: 0;
592
720
  left: 0;
721
+ right: 0;
722
+ bottom: 0;
593
723
  height: 100%;
594
724
  width: 100%;
595
725
  }
726
+
727
+ .close-button {
728
+ background: none;
729
+ border: none;
730
+ font-size: 20px;
731
+ cursor: pointer;
732
+ color: #333;
733
+ }
596
734
  ```
597
735
 
598
736
 
@@ -796,81 +934,209 @@ Hero example:
796
934
 
797
935
  ## Parallax
798
936
 
937
+ Smooth parallax scrolling effect for images and elements, inspired by SimpleParallax.js.
938
+
799
939
  ### Options
800
940
 
801
941
  - `el`
802
942
  - default `[data-parallax]`
943
+ - Can also be `[data-parallax-parent]` for multi-element parallax
944
+
945
+ - `factor`
946
+ - default `1.3`
947
+ - Controls the speed of the parallax effect (higher = more movement)
948
+ - Can be overridden per-element with `data-parallax-factor` attribute
803
949
 
804
950
  - `fadeContent`
951
+ - default `true`
952
+ - If true, fades out content as it leaves the viewport
953
+ - For multi-element parallax, can be set per-element with `data-parallax-fade` attribute
954
+
955
+ - `scale`
956
+ - default `1.2`
957
+ - Scale factor to apply to parallax background images to prevent gaps during movement
958
+
959
+ - `orientation`
960
+ - default `'up'`
961
+ - Direction of parallax movement: `'up'`, `'down'`, `'left'`, or `'right'`
962
+ - Can be overridden per-element with `data-parallax-orientation` attribute
963
+
964
+ - `overflow`
805
965
  - default `false`
806
- - If true, fades out `[data-parallax-content]` as we move towards bottom of parallaxed element
966
+ - Whether to allow element overflow to be visible
807
967
 
968
+ ### Single-Element Parallax
808
969
 
809
- Example:
970
+ For traditional parallax effects with a background and content:
810
971
 
811
972
  ```html
812
973
  <style>
813
974
  [data-parallax] {
814
975
  position: relative;
815
- min-height: 100vh;
976
+ min-height: 70vh;
816
977
  overflow: hidden;
817
978
  }
818
979
 
819
980
  [data-parallax-figure] {
820
981
  position: absolute;
821
- top: 0;
982
+ top: -20%;
822
983
  left: 0;
823
- height: 100%;
824
984
  width: 100%;
825
- max-height: 100%;
826
- }
827
-
828
- [data-parallax-figure] picture {
829
- height: 100%;
830
- width: 100%;
831
- }
832
-
833
- [data-parallax-figure] picture img {
834
- min-height: 100%;
835
- min-width: 100%;
836
- max-height: 100%;
837
- object-fit: cover;
985
+ height: 140%;
986
+ background-size: cover;
987
+ background-position: center;
988
+ will-change: transform;
838
989
  }
839
990
 
840
991
  [data-parallax-content] {
841
992
  position: absolute;
842
993
  top: 0;
843
994
  left: 0;
844
- height: 100%;
845
995
  width: 100%;
996
+ height: 100%;
846
997
  display: flex;
998
+ flex-direction: column;
847
999
  justify-content: center;
848
1000
  align-items: center;
1001
+ color: white;
1002
+ text-align: center;
1003
+ background-color: rgba(0, 0, 0, 0.3);
1004
+ z-index: 1;
1005
+ will-change: transform, opacity;
849
1006
  }
1007
+ </style>
1008
+
1009
+ <section data-parallax>
1010
+ <div
1011
+ data-parallax-figure
1012
+ style="background-image: url('/path/to/image.jpg');"
1013
+ ></div>
1014
+ <div data-parallax-content>
1015
+ <h2>Parallax Title</h2>
1016
+ <p>Parallax content with automatic fade effect</p>
1017
+ </div>
1018
+ </section>
1019
+ ```
850
1020
 
851
- [data-parallax-content] div {
852
- color: #ffffff;
853
- font-size: 4rem;
1021
+ ### Multi-Element Parallax
1022
+
1023
+ For creating parallax effects with multiple elements, each with their own movement speed and fade settings:
1024
+
1025
+ ```html
1026
+ <style>
1027
+ [data-parallax-parent] {
1028
+ position: relative;
1029
+ height: 80vh;
1030
+ overflow: hidden;
1031
+ }
1032
+
1033
+ .parallax-element {
1034
+ position: absolute;
1035
+ width: 200px;
1036
+ height: 200px;
1037
+ display: flex;
1038
+ align-items: center;
1039
+ justify-content: center;
1040
+ color: white;
1041
+ font-weight: bold;
1042
+ text-align: center;
1043
+ border-radius: 10px;
1044
+ will-change: transform, opacity;
1045
+ }
1046
+
1047
+ /* Position elements as needed */
1048
+ .element-1 {
1049
+ background-color: #e74c3c;
1050
+ left: 20%;
1051
+ top: 30%;
1052
+ }
1053
+
1054
+ .element-2 {
1055
+ background-color: #3498db;
1056
+ left: 50%;
1057
+ top: 40%;
1058
+ }
1059
+
1060
+ .element-3 {
1061
+ background-color: #2ecc71;
1062
+ left: 70%;
1063
+ top: 50%;
854
1064
  }
855
1065
  </style>
856
1066
 
1067
+ <div data-parallax-parent>
1068
+ <!-- Slow element moving down -->
1069
+ <div
1070
+ class="parallax-element element-1"
1071
+ data-parallax-factor="0.8"
1072
+ data-parallax-orientation="down"
1073
+ >
1074
+ Slow downward movement
1075
+ </div>
1076
+
1077
+ <!-- Medium element with fade effect -->
1078
+ <div
1079
+ class="parallax-element element-2"
1080
+ data-parallax-factor="1.5"
1081
+ data-parallax-fade
1082
+ >
1083
+ Medium upward movement with fade
1084
+ </div>
1085
+
1086
+ <!-- Fast element without fade moving left -->
1087
+ <div
1088
+ class="parallax-element element-3"
1089
+ data-parallax-factor="2.5"
1090
+ data-parallax-orientation="left"
1091
+ >
1092
+ Fast leftward movement
1093
+ </div>
1094
+ </div>
1095
+ ```
1096
+
1097
+ ### Initialize in JS
1098
+
1099
+ ```js
1100
+ import { Application, Parallax } from '@brandocms/jupiter'
1101
+
1102
+ const app = new Application()
1103
+
1104
+ // Traditional parallax with background image and content
1105
+ const singleParallax = new Parallax(app, {
1106
+ // Default options
1107
+ factor: 1.3,
1108
+ fadeContent: true,
1109
+ scale: 1.2
1110
+ })
1111
+
1112
+ // Multi-element parallax
1113
+ const multiParallax = new Parallax(app, {
1114
+ el: '[data-parallax-parent]',
1115
+ orientation: 'up' // Default movement direction
1116
+ })
1117
+
1118
+ // Cleanup when needed
1119
+ function cleanup() {
1120
+ singleParallax.destroy()
1121
+ multiParallax.destroy()
1122
+ }
1123
+ ```
1124
+
1125
+ ### Using with picture elements
1126
+
1127
+ For using parallax with responsive images:
1128
+
1129
+ ```html
857
1130
  <section data-parallax>
858
1131
  <div data-parallax-figure>
859
- <%= picture_tag(
860
- work.cover,
861
- placeholder: false,
862
- key: :original,
863
- lazyload: true,
864
- srcset: {Kunstnerforbundet.Artists.Work, :cover},
865
- prefix: media_url(),
866
- img_class: "img-fluid",
867
- alt: "#{work.title} (#{work.year}) - #{work.size} - #{work.technique}")
868
- %>
1132
+ <picture>
1133
+ <source media="(min-width: 1200px)" srcset="/images/large.jpg">
1134
+ <source media="(min-width: 768px)" srcset="/images/medium.jpg">
1135
+ <img src="/images/small.jpg" alt="Parallax image">
1136
+ </picture>
869
1137
  </div>
870
1138
  <div data-parallax-content>
871
- <div>
872
- Testing some parallax :)
873
- </div>
1139
+ <h2>Responsive Parallax</h2>
874
1140
  </div>
875
1141
  </section>
876
1142
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brandocms/jupiter",
3
- "version": "3.54.4",
3
+ "version": "4.0.0-beta.1",
4
4
  "description": "Frontend helpers.",
5
5
  "author": "Univers/Twined",
6
6
  "license": "UNLICENSED",
@@ -15,32 +15,42 @@
15
15
  "module": "src/index.js",
16
16
  "browser": "src/index.js",
17
17
  "files": [
18
- "src"
18
+ "src",
19
+ "types"
19
20
  ],
20
21
  "homepage": "https://github.com/brandocms/jupiter#readme",
21
22
  "sideEffects": false,
22
23
  "scripts": {
23
- "test": "jest __tests__ --passWithNoTests",
24
- "coverage": "jest __tests__ --coverage"
25
- },
26
- "jest": {
27
- "testEnvironment": "jsdom",
28
- "transformIgnorePatterns": [
29
- "/node_modules/(?!gsap).+\\.js$"
30
- ]
24
+ "test": "playwright test",
25
+ "types": "tsc",
26
+ "playwright": "playwright test",
27
+ "playwright:ui": "playwright test --ui",
28
+ "playwright:report": "playwright show-report",
29
+ "playwright:moonwalk": "playwright test e2e/moonwalk.spec.js --project=chromium",
30
+ "playwright:lazyload": "playwright test e2e/lazyload.spec.js --project=chromium",
31
+ "playwright:toggler": "playwright test e2e/toggler.spec.js --project=chromium",
32
+ "playwright:breakpoints": "playwright test e2e/breakpoints.spec.js --project=chromium",
33
+ "playwright:typography": "playwright test e2e/typography.spec.js --project=chromium",
34
+ "playwright:popover": "playwright test e2e/popover.spec.js --project=chromium",
35
+ "playwright:popup": "playwright test e2e/popup.spec.js --project=chromium",
36
+ "playwright:dataloader": "playwright test e2e/dataloader.spec.js --project=chromium",
37
+ "playwright:parallax": "playwright test e2e/parallax.spec.js --project=chromium ",
38
+ "vite": "vite",
39
+ "vite:build": "vite build",
40
+ "vite:preview": "vite preview"
31
41
  },
42
+ "types": "types/index.d.ts",
32
43
  "dependencies": {
33
44
  "@egjs/hammerjs": "^2.0.17",
34
45
  "body-scroll-lock": "^4.0.0-beta.0",
35
- "gsap": "3.12.5",
46
+ "gsap": "^3.12.7",
36
47
  "lodash.defaultsdeep": "^4.6.1"
37
48
  },
38
49
  "devDependencies": {
39
- "@babel/core": "^7.25.2",
40
- "@babel/preset-env": "^7.25.3",
41
- "babel-jest": "^29.7.0",
42
- "jest": "^29.7.0",
43
- "jest-environment-jsdom": "^29.7.0"
50
+ "@playwright/test": "^1.51.1",
51
+ "@types/node": "^22.13.10",
52
+ "typescript": "^5.8.2",
53
+ "vite": "^6.2.2"
44
54
  },
45
55
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
46
56
  }
package/src/index.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @file Main entry point for Jupiter - a frontend toolkit for animations and interactions
3
+ * @module jupiter
4
+ */
5
+
1
6
  import { CSSPlugin, Draggable, ScrollToPlugin, ScrollTrigger, gsap } from 'gsap/all'
2
7
 
3
8
  import Hammer from '@egjs/hammerjs'
@@ -1,5 +1,4 @@
1
- import { gsap } from 'gsap'
2
- import { ScrollToPlugin } from 'gsap/ScrollToPlugin'
1
+ import { gsap, ScrollToPlugin } from 'gsap/all'
3
2
  import _defaultsDeep from 'lodash.defaultsdeep'
4
3
  import rafCallback from '../../utils/rafCallback'
5
4
  import prefersReducedMotion from '../../utils/prefersReducedMotion'
@@ -44,7 +43,7 @@ window.onpageshow = (event) => {
44
43
  }
45
44
 
46
45
  const DEFAULT_OPTIONS = {
47
- respectReducedMotion: true,
46
+ respectReducedMotion: false,
48
47
  featureTests: {
49
48
  touch: true,
50
49
  },
@@ -67,6 +66,14 @@ const DEFAULT_OPTIONS = {
67
66
  faderOpts: {
68
67
  fadeIn: (callback = () => {}) => {
69
68
  const fader = document.querySelector('#fader')
69
+ if (!fader) {
70
+ if (window.bfTO) {
71
+ clearTimeout(window.bfTO)
72
+ }
73
+ document.body.classList.remove('unloaded')
74
+ callback()
75
+ return
76
+ }
70
77
  gsap.to(fader, {
71
78
  opacity: 0,
72
79
  ease: 'power1.inOut',
@@ -107,16 +114,18 @@ export default class Application {
107
114
  this.position = {
108
115
  top: 0,
109
116
  left: 0,
117
+ lastTop: 0,
118
+ lastLeft: 0,
110
119
  }
111
120
 
112
121
  this.state = {
113
122
  revealed: false,
114
123
  forcedScroll: false,
124
+ scrollDirection: null,
115
125
  }
116
126
 
117
127
  this.opts = _defaultsDeep(opts, DEFAULT_OPTIONS)
118
128
  this.focusableSelectors = this.opts.focusableSelectors
119
-
120
129
  this.featureTests = new FeatureTests(this, this.opts.featureTests)
121
130
 
122
131
  if (typeof this.opts.breakpointConfig === 'object') {
@@ -662,10 +671,33 @@ export default class Application {
662
671
  return
663
672
  }
664
673
 
674
+ // Store previous position
675
+ this.position.lastTop = this.position.top
676
+ this.position.lastLeft = this.position.left
677
+
678
+ // Get current position
665
679
  this.position.top = window.pageYOffset
666
680
  this.position.left = window.pageXOffset
667
681
 
668
- const evt = new CustomEvent(Events.APPLICATION_SCROLL, e)
682
+ // Determine scroll direction
683
+ if (this.position.top > this.position.lastTop) {
684
+ this.state.scrollDirection = 'down'
685
+ } else if (this.position.top < this.position.lastTop) {
686
+ this.state.scrollDirection = 'up'
687
+ } else if (this.position.left > this.position.lastLeft) {
688
+ this.state.scrollDirection = 'right'
689
+ } else if (this.position.left < this.position.lastLeft) {
690
+ this.state.scrollDirection = 'left'
691
+ }
692
+
693
+ // Create an enhanced event object with additional data
694
+ const detail = {
695
+ scrollDirection: this.state.scrollDirection,
696
+ position: this.position,
697
+ originalEvent: e
698
+ }
699
+
700
+ const evt = new CustomEvent(Events.APPLICATION_SCROLL, { detail })
669
701
  window.dispatchEvent(evt)
670
702
  }
671
703