svelte-aos 0.0.2 → 0.1.0

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/dist/aos.js CHANGED
@@ -1,32 +1,4 @@
1
- // import './styles.css';
2
- // TODO: Improve this device detection logic
3
- function detectDeviceType() {
4
- if (typeof navigator === 'undefined')
5
- return 'desktop';
6
- const ua = navigator.userAgent;
7
- if (/Mobi|Android/i.test(ua)) {
8
- return 'mobile';
9
- }
10
- else if (/Tablet|iPad/i.test(ua)) {
11
- return 'tablet';
12
- }
13
- else {
14
- return 'desktop';
15
- }
16
- }
17
- const computeRootMargin = (anchorPlacement, offset = 0) => {
18
- // simple handling for common placements; expand if needed
19
- if (anchorPlacement === 'top-bottom') {
20
- return `0px 0px -${offset}px 0px`; // trigger when element top hits viewport bottom - offset
21
- }
22
- if (anchorPlacement === 'bottom-top') {
23
- return `-${offset}px 0px 0px 0px`;
24
- }
25
- if (anchorPlacement === 'center-center') {
26
- return `-50% 0px -50% 0px`;
27
- }
28
- return `0px 0px 0px 0px`;
29
- };
1
+ import { computeRootMargin, detectDeviceType } from "./utils.js";
30
2
  const defaultOptions = {
31
3
  delay: 0,
32
4
  easing: 'ease',
@@ -42,173 +14,126 @@ const defaultOptions = {
42
14
  // useClassNames: false,
43
15
  // disableMutationObserver: false
44
16
  };
45
- function setipObserver(params, node) {
46
- if (params?.duration) {
47
- node.style.setProperty('--aos-duration', `${params.duration}ms`);
48
- }
49
- // handle disable option
50
- if (typeof params?.disable === 'boolean' && params.disable) {
51
- node.classList.add('aos-disabled');
52
- }
53
- else if (typeof params?.disable === 'string') {
54
- const deviceType = detectDeviceType();
55
- if (params.disable === deviceType) {
56
- node.classList.add('aos-disabled');
17
+ function isValidDistance(value) {
18
+ const el = document.createElement('div');
19
+ el.style.width = '';
20
+ el.style.width = value;
21
+ return el.style.width !== '';
22
+ }
23
+ class IntersectionObserverManager {
24
+ observer;
25
+ options;
26
+ elements = new Map();
27
+ constructor(options) {
28
+ this.options = options;
29
+ this.observer = new IntersectionObserver(this.handleIntersect.bind(this), {
30
+ threshold: options.threshold,
31
+ rootMargin: computeRootMargin(options.anchorPlacement, options.offset)
32
+ });
33
+ // handle disable option
34
+ if (typeof options?.disable === 'boolean' && options.disable) {
35
+ document.body.classList.add('aos-disabled');
57
36
  }
58
- }
59
- else if (typeof params?.disable === 'function') {
60
- if (params.disable()) {
61
- node.classList.add('aos-disabled');
37
+ else if (typeof options?.disable === 'string') {
38
+ const deviceType = detectDeviceType();
39
+ if (options.disable === deviceType) {
40
+ document.body.classList.add('aos-disabled');
41
+ }
42
+ }
43
+ else if (typeof options?.disable === 'function') {
44
+ if (options.disable()) {
45
+ document.body.classList.add('aos-disabled');
46
+ }
62
47
  }
63
48
  }
64
- if (params?.delay) {
65
- node.style.setProperty('--aos-delay', `${params.delay}ms`);
66
- }
67
- const io = new IntersectionObserver((entries) => {
49
+ handleIntersect(entries) {
68
50
  entries.forEach((entry) => {
69
51
  if (entry.isIntersecting && entry.intersectionRatio > 0.05) {
70
- // handle intersection
71
- entry.target.classList.add('aos-animate');
72
- if (params?.once) {
73
- // why keep observing if we only want once?
74
- io.unobserve(entry.target);
52
+ if (this.elements.has(entry.target)) {
53
+ entry.target.classList.add('aos-animate');
54
+ const elementOptions = this.elements.get(entry.target);
55
+ if (elementOptions?.once) {
56
+ // if only once why keep observing
57
+ this.observer.unobserve(entry.target);
58
+ }
59
+ }
60
+ else {
61
+ // this should never happen if it does don't observe the element
62
+ console.warn('No options found for element:', entry.target);
63
+ this.observer.unobserve(entry.target);
75
64
  }
76
65
  }
77
66
  else {
78
- const once = entry.target.hasAttribute('data-aos-once') || params?.once;
67
+ const once = entry.target.hasAttribute('data-aos-once') || this.options.once;
79
68
  if (!once) {
80
69
  entry.target.classList.remove('aos-animate');
81
70
  }
82
71
  }
83
72
  });
84
- }, {
85
- threshold: params?.threshold ?? 0.1,
86
- rootMargin: computeRootMargin(params?.anchorPlacement ?? 'top-bottom', params?.offset ?? 0)
87
- });
88
- const observeNode = (el) => {
89
- if (!el.classList.contains('aos-animate')) {
90
- // set al lthe custom css variable and values here we do not to do that on intersection
91
- const dataDuration = el.getAttribute('data-aos-duration') || params?.duration;
92
- if (dataDuration) {
93
- el.style.setProperty('--aos-duration', `${dataDuration}ms`);
94
- }
95
- const dataDelay = el.getAttribute('data-aos-delay') || params?.delay;
96
- if (dataDelay) {
97
- el.style.setProperty('--aos-delay', `${dataDelay}ms`);
98
- }
99
- const easing = el.getAttribute('data-aos-easing');
100
- if (!easing && params?.easing) {
101
- el.setAttribute('data-aos-easing', params.easing);
102
- }
103
- io.observe(el);
73
+ }
74
+ observe(element, options) {
75
+ this.elements.set(element, this.options);
76
+ if (options.animation) {
77
+ element.setAttribute('data-aos', options.animation);
104
78
  }
105
- };
106
- // Observe all existing [data-aos] elements
107
- node.querySelectorAll('[data-aos]').forEach(observeNode);
108
- // Watch for new elements or attribute changes
109
- const mo = new MutationObserver((mutations) => {
110
- mutations.forEach((m) => {
111
- // New nodes
112
- m.addedNodes.forEach((n) => {
113
- if (n.nodeType === 1) {
114
- const el = n;
115
- if (el.matches('[data-aos]'))
116
- observeNode(el);
117
- el.querySelectorAll('[data-aos]').forEach(observeNode);
118
- }
119
- });
120
- // Attribute changes
121
- if (m.type === 'attributes' && m.attributeName === 'data-aos') {
122
- const target = m.target;
123
- observeNode(target);
124
- }
125
- });
126
- });
127
- mo.observe(node, {
128
- childList: true,
129
- subtree: true,
130
- // attributes: true,
131
- attributeFilter: ['data-aos']
132
- });
133
- return { io, mo };
79
+ if (options.duration) {
80
+ element.style.setProperty('--aos-duration', `${options.duration}ms`);
81
+ }
82
+ if (options.delay) {
83
+ element.style.setProperty('--aos-delay', `${options.delay}ms`);
84
+ }
85
+ if (options.easing) {
86
+ element.style.setProperty('--aos-easing', options.easing);
87
+ }
88
+ if (options.distance && isValidDistance(options.distance)) {
89
+ element.style.setProperty('--aos-distance', options.distance);
90
+ }
91
+ this.observer.observe(element);
92
+ }
93
+ unobserve(element) {
94
+ this.observer.unobserve(element);
95
+ this.elements.delete(element);
96
+ }
134
97
  }
135
- /**
136
- * Create a Svelte attacheemnt that observes elements with `data-aos` and toggles
137
- * animation classes when they intersect the viewport.
138
- *
139
- * The attachement will:
140
- * - Apply per-element CSS variables (duration/delay) from `data-aos-*` attributes
141
- * or from the `params` defaults.
142
- * - Add the `.aos-animate` class when the element meets the `threshold`/`rootMargin` criteria.
143
- * - Remove `.aos-animate` on exit unless `once` is true (or element has `data-aos-once="true").
144
- * - If `once` is true, the element will be unobserved after its first entry.
145
- * - Applies `params.easing` to elements that do not have `data-aos-easing` set.
146
- *
147
- * @param {Object} [params] - Global observer and animation options.
148
- */
149
- export function aosObserver(params) {
150
- params = { ...defaultOptions, ...params };
151
- return function (node) {
152
- const { io, mo } = setipObserver(params, node);
153
- return () => {
154
- io.disconnect();
155
- mo.disconnect();
156
- };
98
+ let observerManager = null;
99
+ export function initAOS(options = {}) {
100
+ if (typeof window === 'undefined') {
101
+ console.warn('AOS can only be initialized in a browser environment.');
102
+ return;
103
+ }
104
+ const requiredOptions = { ...defaultOptions, ...options };
105
+ if (!observerManager) {
106
+ observerManager = new IntersectionObserverManager(requiredOptions);
107
+ }
108
+ else {
109
+ console.warn('AOS already initialzed, ');
110
+ }
111
+ }
112
+ function observe(node, options) {
113
+ if (observerManager) {
114
+ observerManager.observe(node, options);
115
+ }
116
+ else {
117
+ console.warn('AOS not initialized. Please call initialize it first.');
118
+ }
119
+ return () => observerManager?.unobserve(node);
120
+ }
121
+ export function aosAttachment(options = {
122
+ animation: 'fade'
123
+ }) {
124
+ return (node) => {
125
+ const unobserve = observe(node, options);
126
+ return unobserve;
157
127
  };
158
128
  }
159
- /**
160
- * This is to use if attachements are not available in your svelte setup.
161
- * @param node - HTML element to attach AOS behavior to
162
- *
163
- * @param params - AOS options
164
- * @returns
165
- */
166
- export function aosAction(node, params) {
167
- params = { ...defaultOptions, ...params };
168
- const { io, mo } = setipObserver(params, node);
129
+ export function aosAction(node, options = {
130
+ animation: 'fade'
131
+ }) {
132
+ const unobserve = observe(node, options);
169
133
  return {
170
- update: () => {
171
- // NOTHING
172
- },
134
+ update: () => { },
173
135
  destroy() {
174
- console.log('AOS Action destroyed for node:', node);
175
- io.disconnect();
176
- mo.disconnect();
136
+ unobserve();
177
137
  }
178
138
  };
179
139
  }
180
- /**
181
- * Convert a small configuration object into a map of `data-aos-*` attributes.
182
- *
183
- * Useful for demos or programmatically attaching attributes to an element (e.g. in Svelte
184
- * you can spread the returned object with `{...toAosAttributes({...})}`).
185
- *
186
- * @param {Object} options - Per-element options
187
- * @param {number} [options.delay] - Delay in ms
188
- * @param {number} [options.duration] - Duration in ms
189
- * @param {EasingType} [options.easing] - Easing name
190
- * @param {AnimationType} [options.animation] - Animation name
191
- * @param {boolean} [options.once] - If true, set `data-aos-once` on the element
192
- * @returns {Record<string,string>} Map of attributes (keys are attribute names, values are strings)
193
- *
194
- * @example
195
- * const attrs = toAosAttributes({ animation: 'fade-up', delay: 150, duration: 400 });
196
- * // => { 'data-aos': 'fade-up', 'data-aos-delay': '150', 'data-aos-duration': '400' }
197
- */
198
- export function toAosAttributes(options) {
199
- const attrs = {};
200
- if (options.delay !== undefined) {
201
- attrs['data-aos-delay'] = options.delay.toString();
202
- }
203
- if (options.duration !== undefined) {
204
- attrs['data-aos-duration'] = options.duration.toString();
205
- }
206
- if (options.easing !== undefined) {
207
- attrs['data-aos-easing'] = options.easing;
208
- }
209
- if (options.once) {
210
- attrs['data-aos-once'] = '';
211
- }
212
- attrs['data-aos'] = options.animation ?? 'fade';
213
- return attrs;
214
- }
@@ -0,0 +1,243 @@
1
+ import type {
2
+ AOSOptions,
3
+ AnimationType,
4
+ DisableOption,
5
+ EasingType,
6
+ AnchorPlacement
7
+ } from './types.ts';
8
+ import { computeRootMargin, detectDeviceType } from './utils.ts';
9
+ // import './styles.css';
10
+
11
+ const defaultOptions: Required<AOSOptions> = {
12
+ delay: 0,
13
+ easing: 'ease',
14
+ duration: 600,
15
+ disable: 'mobile',
16
+ once: false,
17
+ anchorPlacement: 'top-bottom',
18
+ offset: 120,
19
+ threshold: 0.1
20
+
21
+ // TODO: implement these later
22
+ // animatedClassName: 'aos-animate',
23
+ // initClassName?: 'aos-init',
24
+ // useClassNames: false,
25
+ // disableMutationObserver: false
26
+ };
27
+
28
+ function initializeIntersectionObserver(params: AOSOptions, node: HTMLElement) {
29
+ if (params?.duration) {
30
+ node.style.setProperty('--aos-duration', `${params.duration}ms`);
31
+ }
32
+
33
+ // handle disable option
34
+ if (typeof params?.disable === 'boolean' && params.disable) {
35
+ node.classList.add('aos-disabled');
36
+ } else if (typeof params?.disable === 'string') {
37
+ const deviceType = detectDeviceType();
38
+ if (params.disable === deviceType) {
39
+ node.classList.add('aos-disabled');
40
+ }
41
+ } else if (typeof params?.disable === 'function') {
42
+ if (params.disable()) {
43
+ node.classList.add('aos-disabled');
44
+ }
45
+ }
46
+
47
+ if (params?.delay) {
48
+ node.style.setProperty('--aos-delay', `${params.delay}ms`);
49
+ }
50
+
51
+ const io = new IntersectionObserver(
52
+ (entries) => {
53
+ entries.forEach((entry) => {
54
+ if (entry.isIntersecting && entry.intersectionRatio > 0.05) {
55
+ // handle intersection
56
+ entry.target.classList.add('aos-animate');
57
+ if (params?.once) {
58
+ // why keep observing if we only want once?
59
+ io.unobserve(entry.target);
60
+ }
61
+ } else {
62
+ const once = entry.target.hasAttribute('data-aos-once') || params?.once;
63
+ if (!once) {
64
+ entry.target.classList.remove('aos-animate');
65
+ }
66
+ }
67
+ });
68
+ },
69
+ {
70
+ threshold: params?.threshold ?? 0.1,
71
+ rootMargin: computeRootMargin(params?.anchorPlacement ?? 'top-bottom', params?.offset ?? 0)
72
+ }
73
+ );
74
+
75
+ const observeNode = (el: HTMLElement) => {
76
+ if (!el.classList.contains('aos-animate')) {
77
+ // set al lthe custom css variable and values here we do not to do that on intersection
78
+ const dataDuration = el.getAttribute('data-aos-duration') || params?.duration;
79
+ if (dataDuration) {
80
+ el.style.setProperty('--aos-duration', `${dataDuration}ms`);
81
+ }
82
+ const dataDelay = el.getAttribute('data-aos-delay') || params?.delay;
83
+ if (dataDelay) {
84
+ el.style.setProperty('--aos-delay', `${dataDelay}ms`);
85
+ }
86
+
87
+ const easing = el.getAttribute('data-aos-easing');
88
+
89
+ if (!easing && params?.easing) {
90
+ el.setAttribute('data-aos-easing', params.easing);
91
+ }
92
+
93
+ io.observe(el);
94
+ }
95
+ };
96
+
97
+ // Observe all existing [data-aos] elements
98
+ node.querySelectorAll<HTMLElement>('[data-aos]').forEach(observeNode);
99
+
100
+ // Watch for new elements or attribute changes
101
+ const mo = new MutationObserver((mutations) => {
102
+ mutations.forEach((m) => {
103
+ // New nodes
104
+ m.addedNodes.forEach((n) => {
105
+ if (n.nodeType === 1) {
106
+ const el = n as HTMLElement;
107
+ if (el.matches('[data-aos]')) observeNode(el);
108
+ el.querySelectorAll<HTMLElement>('[data-aos]').forEach(observeNode);
109
+ }
110
+ });
111
+
112
+ // Attribute changes
113
+ if (m.type === 'attributes' && m.attributeName === 'data-aos') {
114
+ const target = m.target as HTMLElement;
115
+ observeNode(target);
116
+ }
117
+ });
118
+ });
119
+
120
+ mo.observe(node, {
121
+ childList: true,
122
+ subtree: true,
123
+ // attributes: true,
124
+ attributeFilter: ['data-aos']
125
+ });
126
+
127
+ return { io, mo };
128
+ }
129
+
130
+ /**
131
+ * Create a Svelte attachment that observes elements with `data-aos` and toggles
132
+ * animation classes when they intersect the viewport.
133
+ *
134
+ * The attachment will:
135
+ * - Apply per-element CSS variables (duration/delay) from `data-aos-*` attributes
136
+ * or from the `params` defaults.
137
+ * - Add the `.aos-animate` class when the element meets the `threshold`/`rootMargin` criteria.
138
+ * - Remove `.aos-animate` on exit unless `once` is true (or element has `data-aos-once="true").
139
+ * - If `once` is true, the element will be unobserved after its first entry.
140
+ * - Applies `params.easing` to elements that do not have `data-aos-easing` set.
141
+ *
142
+ * @param {Object} [params] - Global observer and animation options.
143
+ */
144
+ export function aosObserver(params?: {
145
+ duration?: number;
146
+ threshold?: number;
147
+ delay?: number;
148
+ once?: boolean;
149
+ disable?: DisableOption;
150
+ anchorPlacement?: AnchorPlacement;
151
+ offset?: number;
152
+ easing?: EasingType;
153
+ }) {
154
+ params = { ...defaultOptions, ...params };
155
+
156
+ return function (node: HTMLElement) {
157
+ const { io, mo } = initializeIntersectionObserver(params!, node);
158
+ return () => {
159
+ io.disconnect();
160
+ mo.disconnect();
161
+ };
162
+ };
163
+ }
164
+
165
+ /**
166
+ * This is to use if attachements are not available in your svelte setup.
167
+ * @param node - HTML element to attach AOS behavior to
168
+ *
169
+ * @param params - AOS options
170
+ * @returns
171
+ */
172
+ export function aosAction(
173
+ node: HTMLElement,
174
+ params?: {
175
+ duration?: number;
176
+ threshold?: number;
177
+ delay?: number;
178
+ once?: boolean;
179
+ disable?: DisableOption;
180
+ anchorPlacement?: AnchorPlacement;
181
+ offset?: number;
182
+ easing?: EasingType;
183
+ }
184
+ ) {
185
+ params = { ...defaultOptions, ...params };
186
+
187
+ const { io, mo } = initializeIntersectionObserver(params!, node);
188
+
189
+ return {
190
+ update: () => {
191
+ // NOTHING
192
+ },
193
+ destroy() {
194
+ io.disconnect();
195
+ mo.disconnect();
196
+ }
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Convert a small configuration object into a map of `data-aos-*` attributes.
202
+ *
203
+ * Useful for demos or programmatically attaching attributes to an element (e.g. in Svelte
204
+ * you can spread the returned object with `{...toAosAttributes({...})}`).
205
+ *
206
+ * @param {Object} options - Per-element options
207
+ * @param {number} [options.delay] - Delay in ms
208
+ * @param {number} [options.duration] - Duration in ms
209
+ * @param {EasingType} [options.easing] - Easing name
210
+ * @param {AnimationType} [options.animation] - Animation name
211
+ * @param {boolean} [options.once] - If true, set `data-aos-once` on the element
212
+ * @returns {Record<string,string>} Map of attributes (keys are attribute names, values are strings)
213
+ *
214
+ * @example
215
+ * const attrs = toAosAttributes({ animation: 'fade-up', delay: 150, duration: 400 });
216
+ * // => { 'data-aos': 'fade-up', 'data-aos-delay': '150', 'data-aos-duration': '400' }
217
+ */
218
+ export function toAosAttributes(options: {
219
+ delay?: number;
220
+ duration?: number;
221
+ easing?: EasingType;
222
+ animation?: AnimationType;
223
+ once?: boolean;
224
+ }): Record<string, string> {
225
+ const attrs: Record<string, string> = {};
226
+ if (options.delay !== undefined) {
227
+ attrs['data-aos-delay'] = options.delay.toString();
228
+ }
229
+ if (options.duration !== undefined) {
230
+ attrs['data-aos-duration'] = options.duration.toString();
231
+ }
232
+ if (options.easing !== undefined) {
233
+ attrs['data-aos-easing'] = options.easing;
234
+ }
235
+
236
+ if (options.once) {
237
+ attrs['data-aos-once'] = '';
238
+ }
239
+
240
+ attrs['data-aos'] = options.animation ?? 'fade';
241
+
242
+ return attrs;
243
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export { aosObserver, aosAction, toAosAttributes } from './aos.ts';
1
+ export { default as AOS } from './AOS.svelte';
2
+ export { aosAttachment, aosAction } from './aos.ts';
2
3
  export type { AOSOptions, AnimationType, EasingType } from './types.ts';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- // Reexport your entry components here
2
- export { aosObserver, aosAction, toAosAttributes } from "./aos.js";
1
+ export { default as AOS } from './AOS.svelte';
2
+ export { aosAttachment, aosAction } from "./aos.js";
@@ -6,36 +6,35 @@
6
6
 
7
7
  @media screen {
8
8
  html:not(.no-js) {
9
- :not(.aos-disabled) {
10
- /* CSS Custom Properties (defaults) */
11
- --aos-duration: 600ms;
12
- --aos-delay: 50ms;
13
- --aos-distance: 100px;
9
+ /* CSS Custom Properties (defaults) */
10
+ --aos-duration: 600ms;
11
+ --aos-delay: 50ms;
12
+ --aos-distance: 100px;
14
13
 
15
- /* Base transition for all [data-aos] elements */
16
- [data-aos] {
17
- transition-duration: var(--aos-duration);
18
- transition-delay: var(--aos-delay);
19
- transition-timing-function: ease;
20
- }
14
+ /* Base transition for all [data-aos] elements */
15
+ [data-aos] {
16
+ transition-duration: var(--aos-duration);
17
+ transition-delay: var(--aos-delay);
18
+ transition-timing-function: ease;
19
+ }
21
20
 
22
- /* Default animated state */
23
- [data-aos].aos-animate {
24
- transform: translate3d(0, 0, 0) scale(1) rotate(0);
25
- }
21
+ /* Default animated state */
22
+ [data-aos].aos-animate {
23
+ transform: translate3d(0, 0, 0) scale(1) rotate(0);
24
+ }
26
25
 
27
- /* Reduced motion support */
28
- @media (prefers-reduced-motion: reduce) {
29
- [data-aos] {
30
- opacity: 1 !important;
31
- transform: none !important;
32
- transition: none !important;
33
- }
26
+ /* Reduced motion support */
27
+ @media (prefers-reduced-motion: reduce) {
28
+ [data-aos] {
29
+ opacity: 1 !important;
30
+ transform: none !important;
31
+ transition: none !important;
34
32
  }
33
+ }
35
34
 
36
- /* Disabled state */
37
- [data-aos-disabled] [data-aos],
38
- [data-aos][data-aos-disabled] {
35
+ /* this is used for the global disable option */
36
+ .aos-disabled {
37
+ * {
39
38
  opacity: 1 !important;
40
39
  transform: none !important;
41
40
  transition: none !important;