svelte-aos 0.0.3 → 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,18 +1,4 @@
1
- import { detectDeviceType } from "./utils.js";
2
- // import './styles.css';
3
- const computeRootMargin = (anchorPlacement, offset = 0) => {
4
- // simple handling for common placements; expand if needed
5
- if (anchorPlacement === 'top-bottom') {
6
- return `0px 0px -${offset}px 0px`; // trigger when element top hits viewport bottom - offset
7
- }
8
- if (anchorPlacement === 'bottom-top') {
9
- return `-${offset}px 0px 0px 0px`;
10
- }
11
- if (anchorPlacement === 'center-center') {
12
- return `-50% 0px -50% 0px`;
13
- }
14
- return `0px 0px 0px 0px`;
15
- };
1
+ import { computeRootMargin, detectDeviceType } from "./utils.js";
16
2
  const defaultOptions = {
17
3
  delay: 0,
18
4
  easing: 'ease',
@@ -28,173 +14,126 @@ const defaultOptions = {
28
14
  // useClassNames: false,
29
15
  // disableMutationObserver: false
30
16
  };
31
- function initializeIntersectionObserver(params, node) {
32
- if (params?.duration) {
33
- node.style.setProperty('--aos-duration', `${params.duration}ms`);
34
- }
35
- // handle disable option
36
- if (typeof params?.disable === 'boolean' && params.disable) {
37
- node.classList.add('aos-disabled');
38
- }
39
- else if (typeof params?.disable === 'string') {
40
- const deviceType = detectDeviceType();
41
- if (params.disable === deviceType) {
42
- 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');
43
36
  }
44
- }
45
- else if (typeof params?.disable === 'function') {
46
- if (params.disable()) {
47
- 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
+ }
48
47
  }
49
48
  }
50
- if (params?.delay) {
51
- node.style.setProperty('--aos-delay', `${params.delay}ms`);
52
- }
53
- const io = new IntersectionObserver((entries) => {
49
+ handleIntersect(entries) {
54
50
  entries.forEach((entry) => {
55
51
  if (entry.isIntersecting && entry.intersectionRatio > 0.05) {
56
- // handle intersection
57
- entry.target.classList.add('aos-animate');
58
- if (params?.once) {
59
- // why keep observing if we only want once?
60
- 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);
61
64
  }
62
65
  }
63
66
  else {
64
- const once = entry.target.hasAttribute('data-aos-once') || params?.once;
67
+ const once = entry.target.hasAttribute('data-aos-once') || this.options.once;
65
68
  if (!once) {
66
69
  entry.target.classList.remove('aos-animate');
67
70
  }
68
71
  }
69
72
  });
70
- }, {
71
- threshold: params?.threshold ?? 0.1,
72
- rootMargin: computeRootMargin(params?.anchorPlacement ?? 'top-bottom', params?.offset ?? 0)
73
- });
74
- const observeNode = (el) => {
75
- if (!el.classList.contains('aos-animate')) {
76
- // set al lthe custom css variable and values here we do not to do that on intersection
77
- const dataDuration = el.getAttribute('data-aos-duration') || params?.duration;
78
- if (dataDuration) {
79
- el.style.setProperty('--aos-duration', `${dataDuration}ms`);
80
- }
81
- const dataDelay = el.getAttribute('data-aos-delay') || params?.delay;
82
- if (dataDelay) {
83
- el.style.setProperty('--aos-delay', `${dataDelay}ms`);
84
- }
85
- const easing = el.getAttribute('data-aos-easing');
86
- if (!easing && params?.easing) {
87
- el.setAttribute('data-aos-easing', params.easing);
88
- }
89
- 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);
90
78
  }
91
- };
92
- // Observe all existing [data-aos] elements
93
- node.querySelectorAll('[data-aos]').forEach(observeNode);
94
- // Watch for new elements or attribute changes
95
- const mo = new MutationObserver((mutations) => {
96
- mutations.forEach((m) => {
97
- // New nodes
98
- m.addedNodes.forEach((n) => {
99
- if (n.nodeType === 1) {
100
- const el = n;
101
- if (el.matches('[data-aos]'))
102
- observeNode(el);
103
- el.querySelectorAll('[data-aos]').forEach(observeNode);
104
- }
105
- });
106
- // Attribute changes
107
- if (m.type === 'attributes' && m.attributeName === 'data-aos') {
108
- const target = m.target;
109
- observeNode(target);
110
- }
111
- });
112
- });
113
- mo.observe(node, {
114
- childList: true,
115
- subtree: true,
116
- // attributes: true,
117
- attributeFilter: ['data-aos']
118
- });
119
- 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
+ }
97
+ }
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
120
  }
121
- /**
122
- * Create a Svelte attachment that observes elements with `data-aos` and toggles
123
- * animation classes when they intersect the viewport.
124
- *
125
- * The attachment will:
126
- * - Apply per-element CSS variables (duration/delay) from `data-aos-*` attributes
127
- * or from the `params` defaults.
128
- * - Add the `.aos-animate` class when the element meets the `threshold`/`rootMargin` criteria.
129
- * - Remove `.aos-animate` on exit unless `once` is true (or element has `data-aos-once="true").
130
- * - If `once` is true, the element will be unobserved after its first entry.
131
- * - Applies `params.easing` to elements that do not have `data-aos-easing` set.
132
- *
133
- * @param {Object} [params] - Global observer and animation options.
134
- */
135
- export function aosObserver(params) {
136
- params = { ...defaultOptions, ...params };
137
- return function (node) {
138
- const { io, mo } = initializeIntersectionObserver(params, node);
139
- return () => {
140
- io.disconnect();
141
- mo.disconnect();
142
- };
121
+ export function aosAttachment(options = {
122
+ animation: 'fade'
123
+ }) {
124
+ return (node) => {
125
+ const unobserve = observe(node, options);
126
+ return unobserve;
143
127
  };
144
128
  }
145
- /**
146
- * This is to use if attachements are not available in your svelte setup.
147
- * @param node - HTML element to attach AOS behavior to
148
- *
149
- * @param params - AOS options
150
- * @returns
151
- */
152
- export function aosAction(node, params) {
153
- params = { ...defaultOptions, ...params };
154
- const { io, mo } = initializeIntersectionObserver(params, node);
129
+ export function aosAction(node, options = {
130
+ animation: 'fade'
131
+ }) {
132
+ const unobserve = observe(node, options);
155
133
  return {
156
- update: () => {
157
- // NOTHING
158
- },
134
+ update: () => { },
159
135
  destroy() {
160
- console.log('AOS Action destroyed for node:', node);
161
- io.disconnect();
162
- mo.disconnect();
136
+ unobserve();
163
137
  }
164
138
  };
165
139
  }
166
- /**
167
- * Convert a small configuration object into a map of `data-aos-*` attributes.
168
- *
169
- * Useful for demos or programmatically attaching attributes to an element (e.g. in Svelte
170
- * you can spread the returned object with `{...toAosAttributes({...})}`).
171
- *
172
- * @param {Object} options - Per-element options
173
- * @param {number} [options.delay] - Delay in ms
174
- * @param {number} [options.duration] - Duration in ms
175
- * @param {EasingType} [options.easing] - Easing name
176
- * @param {AnimationType} [options.animation] - Animation name
177
- * @param {boolean} [options.once] - If true, set `data-aos-once` on the element
178
- * @returns {Record<string,string>} Map of attributes (keys are attribute names, values are strings)
179
- *
180
- * @example
181
- * const attrs = toAosAttributes({ animation: 'fade-up', delay: 150, duration: 400 });
182
- * // => { 'data-aos': 'fade-up', 'data-aos-delay': '150', 'data-aos-duration': '400' }
183
- */
184
- export function toAosAttributes(options) {
185
- const attrs = {};
186
- if (options.delay !== undefined) {
187
- attrs['data-aos-delay'] = options.delay.toString();
188
- }
189
- if (options.duration !== undefined) {
190
- attrs['data-aos-duration'] = options.duration.toString();
191
- }
192
- if (options.easing !== undefined) {
193
- attrs['data-aos-easing'] = options.easing;
194
- }
195
- if (options.once) {
196
- attrs['data-aos-once'] = '';
197
- }
198
- attrs['data-aos'] = options.animation ?? 'fade';
199
- return attrs;
200
- }
@@ -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";
package/dist/utils.d.ts CHANGED
@@ -1 +1,3 @@
1
+ import type { AnchorPlacement } from './types.ts';
1
2
  export declare function detectDeviceType(): 'mobile' | 'tablet' | 'desktop';
3
+ export declare const computeRootMargin: (anchorPlacement: AnchorPlacement, offset?: number) => string;
package/dist/utils.js CHANGED
@@ -13,3 +13,16 @@ export function detectDeviceType() {
13
13
  return 'desktop';
14
14
  }
15
15
  }
16
+ export const computeRootMargin = (anchorPlacement, offset = 0) => {
17
+ // simple handling for common placements; expand if needed
18
+ if (anchorPlacement === 'top-bottom') {
19
+ return `0px 0px -${offset}px 0px`; // trigger when element top hits viewport bottom - offset
20
+ }
21
+ if (anchorPlacement === 'bottom-top') {
22
+ return `-${offset}px 0px 0px 0px`;
23
+ }
24
+ if (anchorPlacement === 'center-center') {
25
+ return `-50% 0px -50% 0px`;
26
+ }
27
+ return `0px 0px 0px 0px`;
28
+ };
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "svelte-aos",
3
- "version": "0.0.3",
3
+ "version": "0.1.0",
4
4
  "license": "MIT",
5
- "description": "Animate On Scroll (AOS) library for Svelte applications. Easily add scroll-based animations to your Svelte components with customizable options.",
5
+ "description": "Svelte Animate On Scroll (AOS) library for Svelte applications. Easily add scroll-based animations to your Svelte components with customizable options.",
6
6
  "author": {
7
7
  "email": "rachid@dailycode.dev",
8
8
  "name": "Rachid Boudjelida"
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "https://github.com/humanshield-sidepack/svelte-aos"
12
+ "url": "git+https://github.com/humanshield-sidepack/svelte-aos.git"
13
13
  },
14
14
  "bugs": {
15
15
  "url": "https://github.com/humanshield-sidepack/svelte-aos/issues"
@@ -31,10 +31,6 @@
31
31
  "types": "./dist/index.d.ts",
32
32
  "svelte": "./dist/index.js"
33
33
  },
34
- "./no-mutation": {
35
- "types": "./dist/no-mutation/index.d.ts",
36
- "svelte": "./dist/no-mutation/index.js"
37
- },
38
34
  "./styles/base.css": {
39
35
  "svelte": "./dist/styles/base.css"
40
36
  },
@@ -94,6 +90,9 @@
94
90
  "lint-staged": {
95
91
  "*.{js,ts,svelte,css,md,json}": "prettier --write"
96
92
  },
93
+ "dependencies": {
94
+ "shiki": "^3.20.0"
95
+ },
97
96
  "scripts": {
98
97
  "dev": "vite dev",
99
98
  "build": "vite build && npm run prepack",
@@ -1,15 +0,0 @@
1
- import type { AnimationType, AOSOptions, EasingType } from '../types.ts';
2
- export declare function initAOS(options?: AOSOptions): void;
3
- export type AOSElementOptions = {
4
- animation?: AnimationType;
5
- duration?: number;
6
- delay?: number;
7
- easing?: EasingType;
8
- once?: boolean;
9
- distance?: string;
10
- };
11
- export declare function aos(options?: AOSElementOptions): (node: HTMLElement) => () => void | undefined;
12
- export declare function aosAction(node: HTMLElement, options?: AOSElementOptions): {
13
- update: () => void;
14
- destroy(): void;
15
- };