lenis 1.1.14-dev.3 → 1.1.14-dev.5

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.
@@ -0,0 +1,159 @@
1
+ "use client";
2
+
3
+ // packages/react/src/provider.tsx
4
+ import Tempus from "@darkroom.engineering/tempus";
5
+ import Lenis from "lenis";
6
+ import {
7
+ createContext,
8
+ forwardRef,
9
+ useCallback,
10
+ useEffect as useEffect2,
11
+ useImperativeHandle,
12
+ useRef,
13
+ useState as useState2
14
+ } from "react";
15
+
16
+ // packages/react/src/store.ts
17
+ import { useEffect, useState } from "react";
18
+ var Store = class {
19
+ constructor(state) {
20
+ this.state = state;
21
+ }
22
+ listeners = [];
23
+ set(state) {
24
+ this.state = state;
25
+ for (let listener of this.listeners) {
26
+ listener(this.state);
27
+ }
28
+ }
29
+ subscribe(listener) {
30
+ this.listeners = [...this.listeners, listener];
31
+ return () => {
32
+ this.listeners = this.listeners.filter((l) => l !== listener);
33
+ };
34
+ }
35
+ get() {
36
+ return this.state;
37
+ }
38
+ };
39
+ function useStore(store) {
40
+ const [state, setState] = useState(store.get());
41
+ useEffect(() => {
42
+ return store.subscribe((state2) => setState(state2));
43
+ }, [store]);
44
+ return state;
45
+ }
46
+
47
+ // packages/react/src/provider.tsx
48
+ import { jsx } from "react/jsx-runtime";
49
+ var LenisContext = createContext(null);
50
+ var rootLenisContextStore = new Store(null);
51
+ var ReactLenis = forwardRef(
52
+ ({
53
+ children,
54
+ root = false,
55
+ options = {},
56
+ autoRaf = true,
57
+ rafPriority = 0,
58
+ className,
59
+ props
60
+ }, ref) => {
61
+ const wrapperRef = useRef(null);
62
+ const contentRef = useRef(null);
63
+ const [lenis, setLenis] = useState2(void 0);
64
+ useImperativeHandle(
65
+ ref,
66
+ () => ({
67
+ wrapper: wrapperRef.current,
68
+ content: contentRef.current,
69
+ lenis
70
+ }),
71
+ [lenis]
72
+ );
73
+ useEffect2(() => {
74
+ const lenis2 = new Lenis({
75
+ ...options,
76
+ ...!root && {
77
+ wrapper: wrapperRef.current,
78
+ content: contentRef.current
79
+ }
80
+ });
81
+ setLenis(lenis2);
82
+ return () => {
83
+ lenis2.destroy();
84
+ setLenis(void 0);
85
+ };
86
+ }, [root, JSON.stringify(options)]);
87
+ useEffect2(() => {
88
+ if (!lenis || !autoRaf) return;
89
+ return Tempus.add((time) => lenis.raf(time), rafPriority);
90
+ }, [lenis, autoRaf, rafPriority]);
91
+ const callbacksRefs = useRef([]);
92
+ const addCallback = useCallback(
93
+ (callback, priority) => {
94
+ callbacksRefs.current.push({ callback, priority });
95
+ callbacksRefs.current.sort((a, b) => a.priority - b.priority);
96
+ },
97
+ []
98
+ );
99
+ const removeCallback = useCallback(
100
+ (callback) => {
101
+ callbacksRefs.current = callbacksRefs.current.filter(
102
+ (cb) => cb.callback !== callback
103
+ );
104
+ },
105
+ []
106
+ );
107
+ useEffect2(() => {
108
+ if (root && lenis) {
109
+ rootLenisContextStore.set({ lenis, addCallback, removeCallback });
110
+ return () => rootLenisContextStore.set(null);
111
+ }
112
+ }, [root, lenis, addCallback, removeCallback]);
113
+ useEffect2(() => {
114
+ if (!lenis) return;
115
+ const onScroll = (data) => {
116
+ for (let i = 0; i < callbacksRefs.current.length; i++) {
117
+ callbacksRefs.current[i]?.callback(data);
118
+ }
119
+ };
120
+ lenis.on("scroll", onScroll);
121
+ return () => {
122
+ lenis.off("scroll", onScroll);
123
+ };
124
+ }, [lenis]);
125
+ return /* @__PURE__ */ jsx(
126
+ LenisContext.Provider,
127
+ {
128
+ value: { lenis, addCallback, removeCallback },
129
+ children: root ? children : /* @__PURE__ */ jsx("div", { ref: wrapperRef, className, ...props, children: /* @__PURE__ */ jsx("div", { ref: contentRef, children }) })
130
+ }
131
+ );
132
+ }
133
+ );
134
+
135
+ // packages/react/src/use-lenis.ts
136
+ import { useContext, useEffect as useEffect3 } from "react";
137
+ var fallbackContext = {};
138
+ function useLenis(callback, deps = [], priority = 0) {
139
+ const localContext = useContext(LenisContext);
140
+ const rootContext = useStore(rootLenisContextStore);
141
+ const currentContext = localContext ?? rootContext ?? fallbackContext;
142
+ const { lenis, addCallback, removeCallback } = currentContext;
143
+ useEffect3(() => {
144
+ if (!callback || !addCallback || !removeCallback || !lenis) return;
145
+ addCallback(callback, priority);
146
+ callback(lenis);
147
+ return () => {
148
+ removeCallback(callback);
149
+ };
150
+ }, [lenis, addCallback, removeCallback, priority, ...deps]);
151
+ return lenis;
152
+ }
153
+ export {
154
+ ReactLenis as Lenis,
155
+ ReactLenis,
156
+ ReactLenis as default,
157
+ useLenis
158
+ };
159
+ //# sourceMappingURL=lenis-react.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../packages/react/src/provider.tsx","../packages/react/src/store.ts","../packages/react/src/use-lenis.ts"],"sourcesContent":["import Tempus from '@darkroom.engineering/tempus'\r\nimport Lenis, { type ScrollCallback } from 'lenis'\r\nimport {\r\n createContext,\r\n forwardRef,\r\n useCallback,\r\n useEffect,\r\n useImperativeHandle,\r\n useRef,\r\n useState,\r\n} from 'react'\r\nimport { Store } from './store'\r\nimport type { LenisContextValue, LenisProps, LenisRef } from './types'\r\n\r\nexport const LenisContext = createContext<LenisContextValue | null>(null)\r\n\r\n/**\r\n * The root store for the lenis context\r\n *\r\n * This store serves as a fallback for the context if it is not available\r\n * and allows us to use the global lenis instance above a provider\r\n */\r\nexport const rootLenisContextStore = new Store<LenisContextValue | null>(null)\r\n\r\n/**\r\n * React component to setup a Lenis instance\r\n */\r\nexport const ReactLenis = forwardRef<LenisRef, LenisProps>(\r\n (\r\n {\r\n children,\r\n root = false,\r\n options = {},\r\n autoRaf = true,\r\n rafPriority = 0,\r\n className,\r\n props,\r\n }: LenisProps,\r\n ref\r\n ) => {\r\n const wrapperRef = useRef<HTMLDivElement | null>(null)\r\n const contentRef = useRef<HTMLDivElement | null>(null)\r\n\r\n const [lenis, setLenis] = useState<Lenis | undefined>(undefined)\r\n\r\n // Setup ref\r\n useImperativeHandle(\r\n ref,\r\n () => ({\r\n wrapper: wrapperRef.current,\r\n content: contentRef.current,\r\n lenis,\r\n }),\r\n [lenis]\r\n )\r\n\r\n // Setup lenis instance\r\n useEffect(() => {\r\n const lenis = new Lenis({\r\n ...options,\r\n ...(!root && {\r\n wrapper: wrapperRef.current!,\r\n content: contentRef.current!,\r\n }),\r\n })\r\n\r\n setLenis(lenis)\r\n\r\n return () => {\r\n lenis.destroy()\r\n setLenis(undefined)\r\n }\r\n }, [root, JSON.stringify(options)])\r\n\r\n // Setup raf\r\n useEffect(() => {\r\n if (!lenis || !autoRaf) return\r\n\r\n return Tempus.add((time: number) => lenis.raf(time), rafPriority)\r\n }, [lenis, autoRaf, rafPriority])\r\n\r\n // Handle callbacks\r\n const callbacksRefs = useRef<\r\n {\r\n callback: ScrollCallback\r\n priority: number\r\n }[]\r\n >([])\r\n\r\n const addCallback: LenisContextValue['addCallback'] = useCallback(\r\n (callback, priority) => {\r\n callbacksRefs.current.push({ callback, priority })\r\n callbacksRefs.current.sort((a, b) => a.priority - b.priority)\r\n },\r\n []\r\n )\r\n\r\n const removeCallback: LenisContextValue['removeCallback'] = useCallback(\r\n (callback) => {\r\n callbacksRefs.current = callbacksRefs.current.filter(\r\n (cb) => cb.callback !== callback\r\n )\r\n },\r\n []\r\n )\r\n\r\n // This makes sure to set the global context if the root is true\r\n useEffect(() => {\r\n if (root && lenis) {\r\n rootLenisContextStore.set({ lenis, addCallback, removeCallback })\r\n\r\n return () => rootLenisContextStore.set(null)\r\n }\r\n }, [root, lenis, addCallback, removeCallback])\r\n\r\n // Setup callback listeners\r\n useEffect(() => {\r\n if (!lenis) return\r\n\r\n const onScroll: ScrollCallback = (data) => {\r\n for (let i = 0; i < callbacksRefs.current.length; i++) {\r\n callbacksRefs.current[i]?.callback(data)\r\n }\r\n }\r\n\r\n lenis.on('scroll', onScroll)\r\n\r\n return () => {\r\n lenis.off('scroll', onScroll)\r\n }\r\n }, [lenis])\r\n\r\n return (\r\n <LenisContext.Provider\r\n value={{ lenis: lenis!, addCallback, removeCallback }}\r\n >\r\n {root ? (\r\n children\r\n ) : (\r\n <div ref={wrapperRef} className={className} {...props}>\r\n <div ref={contentRef}>{children}</div>\r\n </div>\r\n )}\r\n </LenisContext.Provider>\r\n )\r\n }\r\n)\r\n","import { useEffect, useState } from 'react'\r\n\r\ntype Listener<S> = (state: S) => void\r\n\r\nexport class Store<S> {\r\n private listeners: Listener<S>[] = []\r\n\r\n constructor(private state: S) {}\r\n\r\n set(state: S) {\r\n this.state = state\r\n\r\n for (let listener of this.listeners) {\r\n listener(this.state)\r\n }\r\n }\r\n\r\n subscribe(listener: Listener<S>) {\r\n this.listeners = [...this.listeners, listener]\r\n return () => {\r\n this.listeners = this.listeners.filter((l) => l !== listener)\r\n }\r\n }\r\n\r\n get() {\r\n return this.state\r\n }\r\n}\r\n\r\nexport function useStore<S>(store: Store<S>) {\r\n const [state, setState] = useState(store.get())\r\n\r\n useEffect(() => {\r\n return store.subscribe((state) => setState(state))\r\n }, [store])\r\n\r\n return state\r\n}\r\n","import type { ScrollCallback } from 'lenis'\r\nimport { useContext, useEffect } from 'react'\r\nimport { LenisContext, rootLenisContextStore } from './provider'\r\nimport { useStore } from './store'\r\nimport type { LenisContextValue } from './types'\r\n\r\n// Fall back to an empty object if both context and store are not available\r\nconst fallbackContext: Partial<LenisContextValue> = {}\r\n\r\n/**\r\n * Hook to access the Lenis instance and its methods\r\n *\r\n * @example <caption>Scroll callback</caption>\r\n * useLenis((lenis) => {\r\n * if (lenis.isScrolling) {\r\n * console.log('Scrolling...')\r\n * }\r\n *\r\n * if (lenis.progress === 1) {\r\n * console.log('At the end!')\r\n * }\r\n * })\r\n *\r\n * @example <caption>Scroll callback with dependencies</caption>\r\n * useLenis((lenis) => {\r\n * if (lenis.isScrolling) {\r\n * console.log('Scrolling...', someDependency)\r\n * }\r\n * }, [someDependency])\r\n * @example <caption>Scroll callback with priority</caption>\r\n * useLenis((lenis) => {\r\n * if (lenis.isScrolling) {\r\n * console.log('Scrolling...')\r\n * }\r\n * }, [], 1)\r\n * @example <caption>Instance access</caption>\r\n * const lenis = useLenis()\r\n *\r\n * handleClick() {\r\n * lenis.scrollTo(100, {\r\n * lerp: 0.1,\r\n * duration: 1,\r\n * easing: (t) => t,\r\n * onComplete: () => {\r\n * console.log('Complete!')\r\n * }\r\n * })\r\n * }\r\n */\r\nexport function useLenis(\r\n callback?: ScrollCallback,\r\n deps: any[] = [],\r\n priority = 0\r\n) {\r\n // Try to get the lenis instance from the context first\r\n const localContext = useContext(LenisContext)\r\n // Fall back to the root store if the context is not available\r\n const rootContext = useStore(rootLenisContextStore)\r\n // Fall back to the fallback context if all else fails\r\n const currentContext = localContext ?? rootContext ?? fallbackContext\r\n\r\n const { lenis, addCallback, removeCallback } = currentContext\r\n\r\n useEffect(() => {\r\n if (!callback || !addCallback || !removeCallback || !lenis) return\r\n\r\n addCallback(callback, priority)\r\n callback(lenis)\r\n\r\n return () => {\r\n removeCallback(callback)\r\n }\r\n }, [lenis, addCallback, removeCallback, priority, ...deps])\r\n\r\n return lenis\r\n}\r\n"],"mappings":"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","names":["useEffect","useState","state","useState","useEffect","lenis","useEffect","useEffect"]}
@@ -0,0 +1,308 @@
1
+ // packages/snap/src/debounce.ts
2
+ function debounce(callback, delay) {
3
+ let timer;
4
+ return function(...args) {
5
+ let context = this;
6
+ clearTimeout(timer);
7
+ timer = setTimeout(() => {
8
+ timer = void 0;
9
+ callback.apply(context, args);
10
+ }, delay);
11
+ };
12
+ }
13
+
14
+ // packages/snap/src/element.ts
15
+ function removeParentSticky(element) {
16
+ const position = getComputedStyle(element).position;
17
+ const isSticky = position === "sticky";
18
+ if (isSticky) {
19
+ element.style.setProperty("position", "static");
20
+ element.dataset.sticky = "true";
21
+ }
22
+ if (element.offsetParent) {
23
+ removeParentSticky(element.offsetParent);
24
+ }
25
+ }
26
+ function addParentSticky(element) {
27
+ if (element?.dataset?.sticky === "true") {
28
+ element.style.removeProperty("position");
29
+ delete element.dataset.sticky;
30
+ }
31
+ if (element.offsetParent) {
32
+ addParentSticky(element.offsetParent);
33
+ }
34
+ }
35
+ function offsetTop(element, accumulator = 0) {
36
+ const top = accumulator + element.offsetTop;
37
+ if (element.offsetParent) {
38
+ return offsetTop(element.offsetParent, top);
39
+ }
40
+ return top;
41
+ }
42
+ function offsetLeft(element, accumulator = 0) {
43
+ const left = accumulator + element.offsetLeft;
44
+ if (element.offsetParent) {
45
+ return offsetLeft(element.offsetParent, left);
46
+ }
47
+ return left;
48
+ }
49
+ function scrollTop(element, accumulator = 0) {
50
+ const top = accumulator + element.scrollTop;
51
+ if (element.offsetParent) {
52
+ return scrollTop(element.offsetParent, top);
53
+ }
54
+ return top + window.scrollY;
55
+ }
56
+ function scrollLeft(element, accumulator = 0) {
57
+ const left = accumulator + element.scrollLeft;
58
+ if (element.offsetParent) {
59
+ return scrollLeft(element.offsetParent, left);
60
+ }
61
+ return left + window.scrollX;
62
+ }
63
+ var SnapElement = class {
64
+ element;
65
+ options;
66
+ align;
67
+ // @ts-ignore
68
+ rect = {};
69
+ wrapperResizeObserver;
70
+ resizeObserver;
71
+ constructor(element, {
72
+ align = ["start"],
73
+ ignoreSticky = true,
74
+ ignoreTransform = false
75
+ } = {}) {
76
+ this.element = element;
77
+ this.options = { align, ignoreSticky, ignoreTransform };
78
+ this.align = [align].flat();
79
+ this.wrapperResizeObserver = new ResizeObserver(this.onWrapperResize);
80
+ this.wrapperResizeObserver.observe(document.body);
81
+ this.onWrapperResize();
82
+ this.resizeObserver = new ResizeObserver(this.onResize);
83
+ this.resizeObserver.observe(this.element);
84
+ this.setRect({
85
+ width: this.element.offsetWidth,
86
+ height: this.element.offsetHeight
87
+ });
88
+ }
89
+ destroy() {
90
+ this.wrapperResizeObserver.disconnect();
91
+ this.resizeObserver.disconnect();
92
+ }
93
+ setRect({
94
+ top,
95
+ left,
96
+ width,
97
+ height,
98
+ element
99
+ } = {}) {
100
+ top = top ?? this.rect.top;
101
+ left = left ?? this.rect.left;
102
+ width = width ?? this.rect.width;
103
+ height = height ?? this.rect.height;
104
+ element = element ?? this.rect.element;
105
+ if (top === this.rect.top && left === this.rect.left && width === this.rect.width && height === this.rect.height && element === this.rect.element)
106
+ return;
107
+ this.rect.top = top;
108
+ this.rect.y = top;
109
+ this.rect.width = width;
110
+ this.rect.height = height;
111
+ this.rect.left = left;
112
+ this.rect.x = left;
113
+ this.rect.bottom = top + height;
114
+ this.rect.right = left + width;
115
+ }
116
+ onWrapperResize = () => {
117
+ let top, left;
118
+ if (this.options.ignoreSticky) removeParentSticky(this.element);
119
+ if (this.options.ignoreTransform) {
120
+ top = offsetTop(this.element);
121
+ left = offsetLeft(this.element);
122
+ } else {
123
+ const rect = this.element.getBoundingClientRect();
124
+ top = rect.top + scrollTop(this.element);
125
+ left = rect.left + scrollLeft(this.element);
126
+ }
127
+ if (this.options.ignoreSticky) addParentSticky(this.element);
128
+ this.setRect({ top, left });
129
+ };
130
+ onResize = ([entry]) => {
131
+ if (!entry?.borderBoxSize[0]) return;
132
+ const width = entry.borderBoxSize[0].inlineSize;
133
+ const height = entry.borderBoxSize[0].blockSize;
134
+ this.setRect({ width, height });
135
+ };
136
+ };
137
+
138
+ // packages/snap/src/uid.ts
139
+ var index = 0;
140
+ function uid() {
141
+ return index++;
142
+ }
143
+
144
+ // packages/snap/src/snap.ts
145
+ var Snap = class {
146
+ constructor(lenis, {
147
+ type = "mandatory",
148
+ lerp,
149
+ easing,
150
+ duration,
151
+ velocityThreshold = 1,
152
+ debounce: debounceDelay = 0,
153
+ onSnapStart,
154
+ onSnapComplete
155
+ } = {}) {
156
+ this.lenis = lenis;
157
+ this.options = {
158
+ type,
159
+ lerp,
160
+ easing,
161
+ duration,
162
+ velocityThreshold,
163
+ debounce: debounceDelay,
164
+ onSnapStart,
165
+ onSnapComplete
166
+ };
167
+ this.onWindowResize();
168
+ window.addEventListener("resize", this.onWindowResize, false);
169
+ this.onSnapDebounced = debounce(this.onSnap, this.options.debounce);
170
+ this.lenis.on("scroll", this.onScroll);
171
+ }
172
+ options;
173
+ elements = /* @__PURE__ */ new Map();
174
+ snaps = /* @__PURE__ */ new Map();
175
+ viewport = {
176
+ width: window.innerWidth,
177
+ height: window.innerHeight
178
+ };
179
+ isStopped = false;
180
+ onSnapDebounced;
181
+ /**
182
+ * Destroy the snap instance
183
+ */
184
+ destroy() {
185
+ this.lenis.off("scroll", this.onScroll);
186
+ window.removeEventListener("resize", this.onWindowResize, false);
187
+ this.elements.forEach((element) => element.destroy());
188
+ }
189
+ /**
190
+ * Start the snap after it has been stopped
191
+ */
192
+ start() {
193
+ this.isStopped = false;
194
+ }
195
+ /**
196
+ * Stop the snap
197
+ */
198
+ stop() {
199
+ this.isStopped = true;
200
+ }
201
+ /**
202
+ * Add a snap to the snap instance
203
+ *
204
+ * @param value The value to snap to
205
+ * @param userData User data that will be forwarded through the snap event
206
+ * @returns Unsubscribe function
207
+ */
208
+ add(value, userData = {}) {
209
+ const id = uid();
210
+ this.snaps.set(id, { value, userData });
211
+ return () => this.remove(id);
212
+ }
213
+ /**
214
+ * Remove a snap from the snap instance
215
+ *
216
+ * @param id The snap id of the snap to remove
217
+ */
218
+ remove(id) {
219
+ this.snaps.delete(id);
220
+ }
221
+ /**
222
+ * Add an element to the snap instance
223
+ *
224
+ * @param element The element to add
225
+ * @param options The options for the element
226
+ * @returns Unsubscribe function
227
+ */
228
+ addElement(element, options = {}) {
229
+ const id = uid();
230
+ this.elements.set(id, new SnapElement(element, options));
231
+ return () => this.removeElement(id);
232
+ }
233
+ /**
234
+ * Remove an element from the snap instance
235
+ *
236
+ * @param id The snap id of the snap element to remove
237
+ */
238
+ removeElement(id) {
239
+ this.elements.delete(id);
240
+ }
241
+ onWindowResize = () => {
242
+ this.viewport.width = window.innerWidth;
243
+ this.viewport.height = window.innerHeight;
244
+ };
245
+ onScroll = ({
246
+ // scroll,
247
+ // limit,
248
+ lastVelocity,
249
+ velocity,
250
+ // isScrolling,
251
+ userData
252
+ }) => {
253
+ if (this.isStopped) return;
254
+ const isDecelerating = Math.abs(lastVelocity) > Math.abs(velocity);
255
+ const isTurningBack = Math.sign(lastVelocity) !== Math.sign(velocity) && velocity !== 0;
256
+ if (Math.abs(velocity) < this.options.velocityThreshold && // !isTouching &&
257
+ isDecelerating && !isTurningBack && userData?.initiator !== "snap") {
258
+ this.onSnapDebounced();
259
+ }
260
+ };
261
+ onSnap = () => {
262
+ let { scroll, isHorizontal } = this.lenis;
263
+ scroll = Math.ceil(this.lenis.scroll);
264
+ let snaps = [...this.snaps.values()];
265
+ this.elements.forEach(({ rect, align }) => {
266
+ let value;
267
+ align.forEach((align2) => {
268
+ if (align2 === "start") {
269
+ value = rect.top;
270
+ } else if (align2 === "center") {
271
+ value = isHorizontal ? rect.left + rect.width / 2 - this.viewport.width / 2 : rect.top + rect.height / 2 - this.viewport.height / 2;
272
+ } else if (align2 === "end") {
273
+ value = isHorizontal ? rect.left + rect.width - this.viewport.width : rect.top + rect.height - this.viewport.height;
274
+ }
275
+ if (typeof value === "number") {
276
+ snaps.push({ value: Math.ceil(value), userData: {} });
277
+ }
278
+ });
279
+ });
280
+ snaps = snaps.sort((a, b) => Math.abs(a.value) - Math.abs(b.value));
281
+ let prevSnap = snaps.findLast(({ value }) => value <= scroll);
282
+ if (prevSnap === void 0) prevSnap = snaps[0];
283
+ const distanceToPrevSnap = Math.abs(scroll - prevSnap.value);
284
+ let nextSnap = snaps.find(({ value }) => value >= scroll);
285
+ if (nextSnap === void 0) nextSnap = snaps[snaps.length - 1];
286
+ const distanceToNextSnap = Math.abs(scroll - nextSnap.value);
287
+ const snap = distanceToPrevSnap < distanceToNextSnap ? prevSnap : nextSnap;
288
+ const distance = Math.abs(scroll - snap.value);
289
+ if (this.options.type === "mandatory" || this.options.type === "proximity" && distance <= (isHorizontal ? this.lenis.dimensions.width : this.lenis.dimensions.height)) {
290
+ this.lenis.scrollTo(snap.value, {
291
+ lerp: this.options.lerp,
292
+ easing: this.options.easing,
293
+ duration: this.options.duration,
294
+ userData: { initiator: "snap" },
295
+ onStart: () => {
296
+ this.options.onSnapStart?.(snap);
297
+ },
298
+ onComplete: () => {
299
+ this.options.onSnapComplete?.(snap);
300
+ }
301
+ });
302
+ }
303
+ };
304
+ };
305
+
306
+ // packages/snap/browser.ts
307
+ globalThis.Snap = Snap;
308
+ //# sourceMappingURL=lenis-snap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../packages/snap/src/debounce.ts","../packages/snap/src/element.ts","../packages/snap/src/uid.ts","../packages/snap/src/snap.ts","../packages/snap/browser.ts"],"sourcesContent":["export function debounce<CB extends (...args: any[]) => void>(\r\n callback: CB,\r\n delay: number\r\n) {\r\n let timer: number | undefined\r\n return function <T>(this: T, ...args: Parameters<typeof callback>) {\r\n let context = this\r\n clearTimeout(timer)\r\n timer = setTimeout(() => {\r\n timer = undefined\r\n callback.apply(context, args)\r\n }, delay)\r\n }\r\n}\r\n","function removeParentSticky(element: HTMLElement) {\r\n const position = getComputedStyle(element).position\r\n\r\n const isSticky = position === 'sticky'\r\n\r\n if (isSticky) {\r\n element.style.setProperty('position', 'static')\r\n element.dataset.sticky = 'true'\r\n }\r\n\r\n if (element.offsetParent) {\r\n removeParentSticky(element.offsetParent as HTMLElement)\r\n }\r\n}\r\n\r\nfunction addParentSticky(element: HTMLElement) {\r\n if (element?.dataset?.sticky === 'true') {\r\n element.style.removeProperty('position')\r\n delete element.dataset.sticky\r\n }\r\n\r\n if (element.offsetParent) {\r\n addParentSticky(element.offsetParent as HTMLElement)\r\n }\r\n}\r\n\r\nfunction offsetTop(element: HTMLElement, accumulator = 0) {\r\n const top = accumulator + element.offsetTop\r\n if (element.offsetParent) {\r\n return offsetTop(element.offsetParent as HTMLElement, top)\r\n }\r\n return top\r\n}\r\n\r\nfunction offsetLeft(element: HTMLElement, accumulator = 0) {\r\n const left = accumulator + element.offsetLeft\r\n if (element.offsetParent) {\r\n return offsetLeft(element.offsetParent as HTMLElement, left)\r\n }\r\n return left\r\n}\r\n\r\nfunction scrollTop(element: HTMLElement, accumulator = 0) {\r\n const top = accumulator + element.scrollTop\r\n if (element.offsetParent) {\r\n return scrollTop(element.offsetParent as HTMLElement, top)\r\n }\r\n return top + window.scrollY\r\n}\r\n\r\nfunction scrollLeft(element: HTMLElement, accumulator = 0) {\r\n const left = accumulator + element.scrollLeft\r\n if (element.offsetParent) {\r\n return scrollLeft(element.offsetParent as HTMLElement, left)\r\n }\r\n return left + window.scrollX\r\n}\r\n\r\nexport type SnapElementOptions = {\r\n align?: string[]\r\n ignoreSticky?: boolean\r\n ignoreTransform?: boolean\r\n}\r\n\r\ntype Rect = {\r\n top: number\r\n left: number\r\n width: number\r\n height: number\r\n x: number\r\n y: number\r\n bottom: number\r\n right: number\r\n element: HTMLElement\r\n}\r\n\r\nexport class SnapElement {\r\n element: HTMLElement\r\n options: SnapElementOptions\r\n align: string[]\r\n // @ts-ignore\r\n rect: Rect = {}\r\n wrapperResizeObserver: ResizeObserver\r\n resizeObserver: ResizeObserver\r\n\r\n constructor(\r\n element: HTMLElement,\r\n {\r\n align = ['start'],\r\n ignoreSticky = true,\r\n ignoreTransform = false,\r\n }: SnapElementOptions = {}\r\n ) {\r\n this.element = element\r\n\r\n this.options = { align, ignoreSticky, ignoreTransform }\r\n\r\n // this.ignoreSticky = ignoreSticky\r\n // this.ignoreTransform = ignoreTransform\r\n\r\n this.align = [align].flat()\r\n\r\n // TODO: assing rect immediately\r\n\r\n this.wrapperResizeObserver = new ResizeObserver(this.onWrapperResize)\r\n this.wrapperResizeObserver.observe(document.body)\r\n this.onWrapperResize()\r\n\r\n this.resizeObserver = new ResizeObserver(this.onResize)\r\n this.resizeObserver.observe(this.element)\r\n this.setRect({\r\n width: this.element.offsetWidth,\r\n height: this.element.offsetHeight,\r\n })\r\n }\r\n\r\n destroy() {\r\n this.wrapperResizeObserver.disconnect()\r\n this.resizeObserver.disconnect()\r\n }\r\n\r\n setRect({\r\n top,\r\n left,\r\n width,\r\n height,\r\n element,\r\n }: {\r\n top?: number\r\n left?: number\r\n width?: number\r\n height?: number\r\n element?: HTMLElement\r\n } = {}) {\r\n top = top ?? this.rect.top\r\n left = left ?? this.rect.left\r\n width = width ?? this.rect.width\r\n height = height ?? this.rect.height\r\n element = element ?? this.rect.element\r\n\r\n if (\r\n top === this.rect.top &&\r\n left === this.rect.left &&\r\n width === this.rect.width &&\r\n height === this.rect.height &&\r\n element === this.rect.element\r\n )\r\n return\r\n\r\n this.rect.top = top\r\n this.rect.y = top\r\n this.rect.width = width\r\n this.rect.height = height\r\n this.rect.left = left\r\n this.rect.x = left\r\n this.rect.bottom = top + height\r\n this.rect.right = left + width\r\n }\r\n\r\n onWrapperResize = () => {\r\n let top, left\r\n\r\n if (this.options.ignoreSticky) removeParentSticky(this.element)\r\n if (this.options.ignoreTransform) {\r\n top = offsetTop(this.element)\r\n left = offsetLeft(this.element)\r\n } else {\r\n const rect = this.element.getBoundingClientRect()\r\n top = rect.top + scrollTop(this.element)\r\n left = rect.left + scrollLeft(this.element)\r\n }\r\n if (this.options.ignoreSticky) addParentSticky(this.element)\r\n\r\n this.setRect({ top, left })\r\n }\r\n\r\n onResize = ([entry]: ResizeObserverEntry[]) => {\r\n if (!entry?.borderBoxSize[0]) return\r\n const width = entry.borderBoxSize[0].inlineSize\r\n const height = entry.borderBoxSize[0].blockSize\r\n\r\n this.setRect({ width, height })\r\n }\r\n}\r\n","let index = 0\r\n\r\nexport type UID = number\r\n\r\nexport function uid(): UID {\r\n return index++\r\n}\r\n","import type Lenis from 'lenis'\r\nimport type { UserData } from 'lenis'\r\nimport { debounce } from './debounce'\r\nimport type { SnapElementOptions } from './element'\r\nimport { SnapElement } from './element'\r\nimport type { SnapItem, SnapOptions } from './types'\r\nimport type { UID } from './uid'\r\nimport { uid } from './uid'\r\n\r\n// TODO:\r\n// - horizontal\r\n// - fix trackpad snapping too soon due to velocity (fuck Apple)\r\n// - fix wheel scrolling after limits (see console scroll to)\r\n// - fix touch scroll, do not snap when not released\r\n// - arrow, spacebar\r\n\r\ntype RequiredPick<T, F extends keyof T> = Omit<T, F> & Required<Pick<T, F>>\r\n\r\n/**\r\n * Snap class to handle the snap functionality\r\n *\r\n * @example\r\n * const snap = new Snap(lenis, {\r\n * type: 'mandatory', // 'mandatory', 'proximity'\r\n * lerp: 0.1,\r\n * duration: 1,\r\n * easing: (t) => t,\r\n * onSnapStart: (snap) => {\r\n * console.log('onSnapStart', snap)\r\n * },\r\n * onSnapComplete: (snap) => {\r\n * console.log('onSnapComplete', snap)\r\n * },\r\n * })\r\n *\r\n * snap.add(500) // snap at 500px\r\n *\r\n * const removeSnap = snap.add(500)\r\n *\r\n * if (someCondition) {\r\n * removeSnap()\r\n * }\r\n */\r\nexport class Snap {\r\n options: RequiredPick<SnapOptions, 'type' | 'velocityThreshold' | 'debounce'>\r\n elements = new Map<UID, SnapElement>()\r\n snaps = new Map<UID, SnapItem>()\r\n viewport = {\r\n width: window.innerWidth,\r\n height: window.innerHeight,\r\n }\r\n isStopped = false\r\n onSnapDebounced: () => void\r\n\r\n constructor(\r\n private lenis: Lenis,\r\n {\r\n type = 'mandatory',\r\n lerp,\r\n easing,\r\n duration,\r\n velocityThreshold = 1,\r\n debounce: debounceDelay = 0,\r\n onSnapStart,\r\n onSnapComplete,\r\n }: SnapOptions = {}\r\n ) {\r\n this.options = {\r\n type,\r\n lerp,\r\n easing,\r\n duration,\r\n velocityThreshold,\r\n debounce: debounceDelay,\r\n onSnapStart,\r\n onSnapComplete,\r\n }\r\n\r\n this.onWindowResize()\r\n window.addEventListener('resize', this.onWindowResize, false)\r\n\r\n this.onSnapDebounced = debounce(this.onSnap, this.options.debounce)\r\n\r\n this.lenis.on('scroll', this.onScroll)\r\n }\r\n\r\n /**\r\n * Destroy the snap instance\r\n */\r\n destroy() {\r\n this.lenis.off('scroll', this.onScroll)\r\n window.removeEventListener('resize', this.onWindowResize, false)\r\n this.elements.forEach((element) => element.destroy())\r\n }\r\n\r\n /**\r\n * Start the snap after it has been stopped\r\n */\r\n start() {\r\n this.isStopped = false\r\n }\r\n\r\n /**\r\n * Stop the snap\r\n */\r\n stop() {\r\n this.isStopped = true\r\n }\r\n\r\n /**\r\n * Add a snap to the snap instance\r\n *\r\n * @param value The value to snap to\r\n * @param userData User data that will be forwarded through the snap event\r\n * @returns Unsubscribe function\r\n */\r\n add(value: number, userData: UserData = {}) {\r\n const id = uid()\r\n\r\n this.snaps.set(id, { value, userData })\r\n\r\n return () => this.remove(id)\r\n }\r\n\r\n /**\r\n * Remove a snap from the snap instance\r\n *\r\n * @param id The snap id of the snap to remove\r\n */\r\n remove(id: UID) {\r\n this.snaps.delete(id)\r\n }\r\n\r\n /**\r\n * Add an element to the snap instance\r\n *\r\n * @param element The element to add\r\n * @param options The options for the element\r\n * @returns Unsubscribe function\r\n */\r\n addElement(element: HTMLElement, options = {} as SnapElementOptions) {\r\n const id = uid()\r\n\r\n this.elements.set(id, new SnapElement(element, options))\r\n\r\n return () => this.removeElement(id)\r\n }\r\n\r\n /**\r\n * Remove an element from the snap instance\r\n *\r\n * @param id The snap id of the snap element to remove\r\n */\r\n removeElement(id: UID) {\r\n this.elements.delete(id)\r\n }\r\n\r\n private onWindowResize = () => {\r\n this.viewport.width = window.innerWidth\r\n this.viewport.height = window.innerHeight\r\n }\r\n\r\n private onScroll = ({\r\n // scroll,\r\n // limit,\r\n lastVelocity,\r\n velocity,\r\n // isScrolling,\r\n userData,\r\n }: // isHorizontal,\r\n Lenis) => {\r\n if (this.isStopped) return\r\n\r\n // return\r\n const isDecelerating = Math.abs(lastVelocity) > Math.abs(velocity)\r\n const isTurningBack =\r\n Math.sign(lastVelocity) !== Math.sign(velocity) && velocity !== 0\r\n\r\n if (\r\n Math.abs(velocity) < this.options.velocityThreshold &&\r\n // !isTouching &&\r\n isDecelerating &&\r\n !isTurningBack &&\r\n userData?.initiator !== 'snap'\r\n ) {\r\n this.onSnapDebounced()\r\n }\r\n }\r\n\r\n private onSnap = () => {\r\n let { scroll, isHorizontal } = this.lenis\r\n scroll = Math.ceil(this.lenis.scroll)\r\n\r\n let snaps = [...this.snaps.values()] as SnapItem[]\r\n\r\n this.elements.forEach(({ rect, align }) => {\r\n let value: number | undefined\r\n\r\n align.forEach((align) => {\r\n if (align === 'start') {\r\n value = rect.top\r\n } else if (align === 'center') {\r\n value = isHorizontal\r\n ? rect.left + rect.width / 2 - this.viewport.width / 2\r\n : rect.top + rect.height / 2 - this.viewport.height / 2\r\n } else if (align === 'end') {\r\n value = isHorizontal\r\n ? rect.left + rect.width - this.viewport.width\r\n : rect.top + rect.height - this.viewport.height\r\n }\r\n\r\n if (typeof value === 'number') {\r\n snaps.push({ value: Math.ceil(value), userData: {} })\r\n }\r\n })\r\n })\r\n\r\n snaps = snaps.sort((a, b) => Math.abs(a.value) - Math.abs(b.value))\r\n\r\n let prevSnap = snaps.findLast(({ value }) => value <= scroll)\r\n if (prevSnap === undefined) prevSnap = snaps[0]!\r\n const distanceToPrevSnap = Math.abs(scroll - prevSnap.value)\r\n\r\n let nextSnap = snaps.find(({ value }) => value >= scroll)\r\n if (nextSnap === undefined) nextSnap = snaps[snaps.length - 1]!\r\n const distanceToNextSnap = Math.abs(scroll - nextSnap.value)\r\n\r\n const snap = distanceToPrevSnap < distanceToNextSnap ? prevSnap : nextSnap\r\n\r\n const distance = Math.abs(scroll - snap.value)\r\n\r\n if (\r\n this.options.type === 'mandatory' ||\r\n (this.options.type === 'proximity' &&\r\n distance <=\r\n (isHorizontal\r\n ? this.lenis.dimensions.width\r\n : this.lenis.dimensions.height))\r\n ) {\r\n // this.__isScrolling = true\r\n // this.onSnapStart?.(snap)\r\n\r\n // console.log('scroll to')\r\n\r\n this.lenis.scrollTo(snap.value, {\r\n lerp: this.options.lerp,\r\n easing: this.options.easing,\r\n duration: this.options.duration,\r\n userData: { initiator: 'snap' },\r\n onStart: () => {\r\n this.options.onSnapStart?.(snap)\r\n },\r\n onComplete: () => {\r\n this.options.onSnapComplete?.(snap)\r\n },\r\n })\r\n }\r\n\r\n // console.timeEnd('scroll')\r\n }\r\n}\r\n","// This file serves as an entry point for the package\r\nimport { Snap } from './src/snap'\r\nglobalThis.Snap = Snap\r\n"],"mappings":"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","names":["align"]}
@@ -0,0 +1,2 @@
1
+ function v(s,e){let t;return function(...i){let n=this;clearTimeout(t),t=setTimeout(()=>{t=void 0,s.apply(n,i)},e)}}function w(s){getComputedStyle(s).position==="sticky"&&(s.style.setProperty("position","static"),s.dataset.sticky="true"),s.offsetParent&&w(s.offsetParent)}function S(s){s?.dataset?.sticky==="true"&&(s.style.removeProperty("position"),delete s.dataset.sticky),s.offsetParent&&S(s.offsetParent)}function y(s,e=0){let t=e+s.offsetTop;return s.offsetParent?y(s.offsetParent,t):t}function T(s,e=0){let t=e+s.offsetLeft;return s.offsetParent?T(s.offsetParent,t):t}function g(s,e=0){let t=e+s.scrollTop;return s.offsetParent?g(s.offsetParent,t):t+window.scrollY}function E(s,e=0){let t=e+s.scrollLeft;return s.offsetParent?E(s.offsetParent,t):t+window.scrollX}var l=class{element;options;align;rect={};wrapperResizeObserver;resizeObserver;constructor(e,{align:t=["start"],ignoreSticky:i=!0,ignoreTransform:n=!1}={}){this.element=e,this.options={align:t,ignoreSticky:i,ignoreTransform:n},this.align=[t].flat(),this.wrapperResizeObserver=new ResizeObserver(this.onWrapperResize),this.wrapperResizeObserver.observe(document.body),this.onWrapperResize(),this.resizeObserver=new ResizeObserver(this.onResize),this.resizeObserver.observe(this.element),this.setRect({width:this.element.offsetWidth,height:this.element.offsetHeight})}destroy(){this.wrapperResizeObserver.disconnect(),this.resizeObserver.disconnect()}setRect({top:e,left:t,width:i,height:n,element:r}={}){e=e??this.rect.top,t=t??this.rect.left,i=i??this.rect.width,n=n??this.rect.height,r=r??this.rect.element,!(e===this.rect.top&&t===this.rect.left&&i===this.rect.width&&n===this.rect.height&&r===this.rect.element)&&(this.rect.top=e,this.rect.y=e,this.rect.width=i,this.rect.height=n,this.rect.left=t,this.rect.x=t,this.rect.bottom=e+n,this.rect.right=t+i)}onWrapperResize=()=>{let e,t;if(this.options.ignoreSticky&&w(this.element),this.options.ignoreTransform)e=y(this.element),t=T(this.element);else{let i=this.element.getBoundingClientRect();e=i.top+g(this.element),t=i.left+E(this.element)}this.options.ignoreSticky&&S(this.element),this.setRect({top:e,left:t})};onResize=([e])=>{if(!e?.borderBoxSize[0])return;let t=e.borderBoxSize[0].inlineSize,i=e.borderBoxSize[0].blockSize;this.setRect({width:t,height:i})}};var M=0;function b(){return M++}var f=class{constructor(e,{type:t="mandatory",lerp:i,easing:n,duration:r,velocityThreshold:a=1,debounce:c=0,onSnapStart:h,onSnapComplete:d}={}){this.lenis=e;this.options={type:t,lerp:i,easing:n,duration:r,velocityThreshold:a,debounce:c,onSnapStart:h,onSnapComplete:d},this.onWindowResize(),window.addEventListener("resize",this.onWindowResize,!1),this.onSnapDebounced=v(this.onSnap,this.options.debounce),this.lenis.on("scroll",this.onScroll)}options;elements=new Map;snaps=new Map;viewport={width:window.innerWidth,height:window.innerHeight};isStopped=!1;onSnapDebounced;destroy(){this.lenis.off("scroll",this.onScroll),window.removeEventListener("resize",this.onWindowResize,!1),this.elements.forEach(e=>e.destroy())}start(){this.isStopped=!1}stop(){this.isStopped=!0}add(e,t={}){let i=b();return this.snaps.set(i,{value:e,userData:t}),()=>this.remove(i)}remove(e){this.snaps.delete(e)}addElement(e,t={}){let i=b();return this.elements.set(i,new l(e,t)),()=>this.removeElement(i)}removeElement(e){this.elements.delete(e)}onWindowResize=()=>{this.viewport.width=window.innerWidth,this.viewport.height=window.innerHeight};onScroll=({lastVelocity:e,velocity:t,userData:i})=>{if(this.isStopped)return;let n=Math.abs(e)>Math.abs(t),r=Math.sign(e)!==Math.sign(t)&&t!==0;Math.abs(t)<this.options.velocityThreshold&&n&&!r&&i?.initiator!=="snap"&&this.onSnapDebounced()};onSnap=()=>{let{scroll:e,isHorizontal:t}=this.lenis;e=Math.ceil(this.lenis.scroll);let i=[...this.snaps.values()];this.elements.forEach(({rect:o,align:m})=>{let p;m.forEach(u=>{u==="start"?p=o.top:u==="center"?p=t?o.left+o.width/2-this.viewport.width/2:o.top+o.height/2-this.viewport.height/2:u==="end"&&(p=t?o.left+o.width-this.viewport.width:o.top+o.height-this.viewport.height),typeof p=="number"&&i.push({value:Math.ceil(p),userData:{}})})}),i=i.sort((o,m)=>Math.abs(o.value)-Math.abs(m.value));let n=i.findLast(({value:o})=>o<=e);n===void 0&&(n=i[0]);let r=Math.abs(e-n.value),a=i.find(({value:o})=>o>=e);a===void 0&&(a=i[i.length-1]);let c=Math.abs(e-a.value),h=r<c?n:a,d=Math.abs(e-h.value);(this.options.type==="mandatory"||this.options.type==="proximity"&&d<=(t?this.lenis.dimensions.width:this.lenis.dimensions.height))&&this.lenis.scrollTo(h.value,{lerp:this.options.lerp,easing:this.options.easing,duration:this.options.duration,userData:{initiator:"snap"},onStart:()=>{this.options.onSnapStart?.(h)},onComplete:()=>{this.options.onSnapComplete?.(h)}})}};globalThis.Snap=f;
2
+ //# sourceMappingURL=lenis-snap.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../packages/snap/src/debounce.ts","../packages/snap/src/element.ts","../packages/snap/src/uid.ts","../packages/snap/src/snap.ts","../packages/snap/browser.ts"],"sourcesContent":["export function debounce<CB extends (...args: any[]) => void>(\r\n callback: CB,\r\n delay: number\r\n) {\r\n let timer: number | undefined\r\n return function <T>(this: T, ...args: Parameters<typeof callback>) {\r\n let context = this\r\n clearTimeout(timer)\r\n timer = setTimeout(() => {\r\n timer = undefined\r\n callback.apply(context, args)\r\n }, delay)\r\n }\r\n}\r\n","function removeParentSticky(element: HTMLElement) {\r\n const position = getComputedStyle(element).position\r\n\r\n const isSticky = position === 'sticky'\r\n\r\n if (isSticky) {\r\n element.style.setProperty('position', 'static')\r\n element.dataset.sticky = 'true'\r\n }\r\n\r\n if (element.offsetParent) {\r\n removeParentSticky(element.offsetParent as HTMLElement)\r\n }\r\n}\r\n\r\nfunction addParentSticky(element: HTMLElement) {\r\n if (element?.dataset?.sticky === 'true') {\r\n element.style.removeProperty('position')\r\n delete element.dataset.sticky\r\n }\r\n\r\n if (element.offsetParent) {\r\n addParentSticky(element.offsetParent as HTMLElement)\r\n }\r\n}\r\n\r\nfunction offsetTop(element: HTMLElement, accumulator = 0) {\r\n const top = accumulator + element.offsetTop\r\n if (element.offsetParent) {\r\n return offsetTop(element.offsetParent as HTMLElement, top)\r\n }\r\n return top\r\n}\r\n\r\nfunction offsetLeft(element: HTMLElement, accumulator = 0) {\r\n const left = accumulator + element.offsetLeft\r\n if (element.offsetParent) {\r\n return offsetLeft(element.offsetParent as HTMLElement, left)\r\n }\r\n return left\r\n}\r\n\r\nfunction scrollTop(element: HTMLElement, accumulator = 0) {\r\n const top = accumulator + element.scrollTop\r\n if (element.offsetParent) {\r\n return scrollTop(element.offsetParent as HTMLElement, top)\r\n }\r\n return top + window.scrollY\r\n}\r\n\r\nfunction scrollLeft(element: HTMLElement, accumulator = 0) {\r\n const left = accumulator + element.scrollLeft\r\n if (element.offsetParent) {\r\n return scrollLeft(element.offsetParent as HTMLElement, left)\r\n }\r\n return left + window.scrollX\r\n}\r\n\r\nexport type SnapElementOptions = {\r\n align?: string[]\r\n ignoreSticky?: boolean\r\n ignoreTransform?: boolean\r\n}\r\n\r\ntype Rect = {\r\n top: number\r\n left: number\r\n width: number\r\n height: number\r\n x: number\r\n y: number\r\n bottom: number\r\n right: number\r\n element: HTMLElement\r\n}\r\n\r\nexport class SnapElement {\r\n element: HTMLElement\r\n options: SnapElementOptions\r\n align: string[]\r\n // @ts-ignore\r\n rect: Rect = {}\r\n wrapperResizeObserver: ResizeObserver\r\n resizeObserver: ResizeObserver\r\n\r\n constructor(\r\n element: HTMLElement,\r\n {\r\n align = ['start'],\r\n ignoreSticky = true,\r\n ignoreTransform = false,\r\n }: SnapElementOptions = {}\r\n ) {\r\n this.element = element\r\n\r\n this.options = { align, ignoreSticky, ignoreTransform }\r\n\r\n // this.ignoreSticky = ignoreSticky\r\n // this.ignoreTransform = ignoreTransform\r\n\r\n this.align = [align].flat()\r\n\r\n // TODO: assing rect immediately\r\n\r\n this.wrapperResizeObserver = new ResizeObserver(this.onWrapperResize)\r\n this.wrapperResizeObserver.observe(document.body)\r\n this.onWrapperResize()\r\n\r\n this.resizeObserver = new ResizeObserver(this.onResize)\r\n this.resizeObserver.observe(this.element)\r\n this.setRect({\r\n width: this.element.offsetWidth,\r\n height: this.element.offsetHeight,\r\n })\r\n }\r\n\r\n destroy() {\r\n this.wrapperResizeObserver.disconnect()\r\n this.resizeObserver.disconnect()\r\n }\r\n\r\n setRect({\r\n top,\r\n left,\r\n width,\r\n height,\r\n element,\r\n }: {\r\n top?: number\r\n left?: number\r\n width?: number\r\n height?: number\r\n element?: HTMLElement\r\n } = {}) {\r\n top = top ?? this.rect.top\r\n left = left ?? this.rect.left\r\n width = width ?? this.rect.width\r\n height = height ?? this.rect.height\r\n element = element ?? this.rect.element\r\n\r\n if (\r\n top === this.rect.top &&\r\n left === this.rect.left &&\r\n width === this.rect.width &&\r\n height === this.rect.height &&\r\n element === this.rect.element\r\n )\r\n return\r\n\r\n this.rect.top = top\r\n this.rect.y = top\r\n this.rect.width = width\r\n this.rect.height = height\r\n this.rect.left = left\r\n this.rect.x = left\r\n this.rect.bottom = top + height\r\n this.rect.right = left + width\r\n }\r\n\r\n onWrapperResize = () => {\r\n let top, left\r\n\r\n if (this.options.ignoreSticky) removeParentSticky(this.element)\r\n if (this.options.ignoreTransform) {\r\n top = offsetTop(this.element)\r\n left = offsetLeft(this.element)\r\n } else {\r\n const rect = this.element.getBoundingClientRect()\r\n top = rect.top + scrollTop(this.element)\r\n left = rect.left + scrollLeft(this.element)\r\n }\r\n if (this.options.ignoreSticky) addParentSticky(this.element)\r\n\r\n this.setRect({ top, left })\r\n }\r\n\r\n onResize = ([entry]: ResizeObserverEntry[]) => {\r\n if (!entry?.borderBoxSize[0]) return\r\n const width = entry.borderBoxSize[0].inlineSize\r\n const height = entry.borderBoxSize[0].blockSize\r\n\r\n this.setRect({ width, height })\r\n }\r\n}\r\n","let index = 0\r\n\r\nexport type UID = number\r\n\r\nexport function uid(): UID {\r\n return index++\r\n}\r\n","import type Lenis from 'lenis'\r\nimport type { UserData } from 'lenis'\r\nimport { debounce } from './debounce'\r\nimport type { SnapElementOptions } from './element'\r\nimport { SnapElement } from './element'\r\nimport type { SnapItem, SnapOptions } from './types'\r\nimport type { UID } from './uid'\r\nimport { uid } from './uid'\r\n\r\n// TODO:\r\n// - horizontal\r\n// - fix trackpad snapping too soon due to velocity (fuck Apple)\r\n// - fix wheel scrolling after limits (see console scroll to)\r\n// - fix touch scroll, do not snap when not released\r\n// - arrow, spacebar\r\n\r\ntype RequiredPick<T, F extends keyof T> = Omit<T, F> & Required<Pick<T, F>>\r\n\r\n/**\r\n * Snap class to handle the snap functionality\r\n *\r\n * @example\r\n * const snap = new Snap(lenis, {\r\n * type: 'mandatory', // 'mandatory', 'proximity'\r\n * lerp: 0.1,\r\n * duration: 1,\r\n * easing: (t) => t,\r\n * onSnapStart: (snap) => {\r\n * console.log('onSnapStart', snap)\r\n * },\r\n * onSnapComplete: (snap) => {\r\n * console.log('onSnapComplete', snap)\r\n * },\r\n * })\r\n *\r\n * snap.add(500) // snap at 500px\r\n *\r\n * const removeSnap = snap.add(500)\r\n *\r\n * if (someCondition) {\r\n * removeSnap()\r\n * }\r\n */\r\nexport class Snap {\r\n options: RequiredPick<SnapOptions, 'type' | 'velocityThreshold' | 'debounce'>\r\n elements = new Map<UID, SnapElement>()\r\n snaps = new Map<UID, SnapItem>()\r\n viewport = {\r\n width: window.innerWidth,\r\n height: window.innerHeight,\r\n }\r\n isStopped = false\r\n onSnapDebounced: () => void\r\n\r\n constructor(\r\n private lenis: Lenis,\r\n {\r\n type = 'mandatory',\r\n lerp,\r\n easing,\r\n duration,\r\n velocityThreshold = 1,\r\n debounce: debounceDelay = 0,\r\n onSnapStart,\r\n onSnapComplete,\r\n }: SnapOptions = {}\r\n ) {\r\n this.options = {\r\n type,\r\n lerp,\r\n easing,\r\n duration,\r\n velocityThreshold,\r\n debounce: debounceDelay,\r\n onSnapStart,\r\n onSnapComplete,\r\n }\r\n\r\n this.onWindowResize()\r\n window.addEventListener('resize', this.onWindowResize, false)\r\n\r\n this.onSnapDebounced = debounce(this.onSnap, this.options.debounce)\r\n\r\n this.lenis.on('scroll', this.onScroll)\r\n }\r\n\r\n /**\r\n * Destroy the snap instance\r\n */\r\n destroy() {\r\n this.lenis.off('scroll', this.onScroll)\r\n window.removeEventListener('resize', this.onWindowResize, false)\r\n this.elements.forEach((element) => element.destroy())\r\n }\r\n\r\n /**\r\n * Start the snap after it has been stopped\r\n */\r\n start() {\r\n this.isStopped = false\r\n }\r\n\r\n /**\r\n * Stop the snap\r\n */\r\n stop() {\r\n this.isStopped = true\r\n }\r\n\r\n /**\r\n * Add a snap to the snap instance\r\n *\r\n * @param value The value to snap to\r\n * @param userData User data that will be forwarded through the snap event\r\n * @returns Unsubscribe function\r\n */\r\n add(value: number, userData: UserData = {}) {\r\n const id = uid()\r\n\r\n this.snaps.set(id, { value, userData })\r\n\r\n return () => this.remove(id)\r\n }\r\n\r\n /**\r\n * Remove a snap from the snap instance\r\n *\r\n * @param id The snap id of the snap to remove\r\n */\r\n remove(id: UID) {\r\n this.snaps.delete(id)\r\n }\r\n\r\n /**\r\n * Add an element to the snap instance\r\n *\r\n * @param element The element to add\r\n * @param options The options for the element\r\n * @returns Unsubscribe function\r\n */\r\n addElement(element: HTMLElement, options = {} as SnapElementOptions) {\r\n const id = uid()\r\n\r\n this.elements.set(id, new SnapElement(element, options))\r\n\r\n return () => this.removeElement(id)\r\n }\r\n\r\n /**\r\n * Remove an element from the snap instance\r\n *\r\n * @param id The snap id of the snap element to remove\r\n */\r\n removeElement(id: UID) {\r\n this.elements.delete(id)\r\n }\r\n\r\n private onWindowResize = () => {\r\n this.viewport.width = window.innerWidth\r\n this.viewport.height = window.innerHeight\r\n }\r\n\r\n private onScroll = ({\r\n // scroll,\r\n // limit,\r\n lastVelocity,\r\n velocity,\r\n // isScrolling,\r\n userData,\r\n }: // isHorizontal,\r\n Lenis) => {\r\n if (this.isStopped) return\r\n\r\n // return\r\n const isDecelerating = Math.abs(lastVelocity) > Math.abs(velocity)\r\n const isTurningBack =\r\n Math.sign(lastVelocity) !== Math.sign(velocity) && velocity !== 0\r\n\r\n if (\r\n Math.abs(velocity) < this.options.velocityThreshold &&\r\n // !isTouching &&\r\n isDecelerating &&\r\n !isTurningBack &&\r\n userData?.initiator !== 'snap'\r\n ) {\r\n this.onSnapDebounced()\r\n }\r\n }\r\n\r\n private onSnap = () => {\r\n let { scroll, isHorizontal } = this.lenis\r\n scroll = Math.ceil(this.lenis.scroll)\r\n\r\n let snaps = [...this.snaps.values()] as SnapItem[]\r\n\r\n this.elements.forEach(({ rect, align }) => {\r\n let value: number | undefined\r\n\r\n align.forEach((align) => {\r\n if (align === 'start') {\r\n value = rect.top\r\n } else if (align === 'center') {\r\n value = isHorizontal\r\n ? rect.left + rect.width / 2 - this.viewport.width / 2\r\n : rect.top + rect.height / 2 - this.viewport.height / 2\r\n } else if (align === 'end') {\r\n value = isHorizontal\r\n ? rect.left + rect.width - this.viewport.width\r\n : rect.top + rect.height - this.viewport.height\r\n }\r\n\r\n if (typeof value === 'number') {\r\n snaps.push({ value: Math.ceil(value), userData: {} })\r\n }\r\n })\r\n })\r\n\r\n snaps = snaps.sort((a, b) => Math.abs(a.value) - Math.abs(b.value))\r\n\r\n let prevSnap = snaps.findLast(({ value }) => value <= scroll)\r\n if (prevSnap === undefined) prevSnap = snaps[0]!\r\n const distanceToPrevSnap = Math.abs(scroll - prevSnap.value)\r\n\r\n let nextSnap = snaps.find(({ value }) => value >= scroll)\r\n if (nextSnap === undefined) nextSnap = snaps[snaps.length - 1]!\r\n const distanceToNextSnap = Math.abs(scroll - nextSnap.value)\r\n\r\n const snap = distanceToPrevSnap < distanceToNextSnap ? prevSnap : nextSnap\r\n\r\n const distance = Math.abs(scroll - snap.value)\r\n\r\n if (\r\n this.options.type === 'mandatory' ||\r\n (this.options.type === 'proximity' &&\r\n distance <=\r\n (isHorizontal\r\n ? this.lenis.dimensions.width\r\n : this.lenis.dimensions.height))\r\n ) {\r\n // this.__isScrolling = true\r\n // this.onSnapStart?.(snap)\r\n\r\n // console.log('scroll to')\r\n\r\n this.lenis.scrollTo(snap.value, {\r\n lerp: this.options.lerp,\r\n easing: this.options.easing,\r\n duration: this.options.duration,\r\n userData: { initiator: 'snap' },\r\n onStart: () => {\r\n this.options.onSnapStart?.(snap)\r\n },\r\n onComplete: () => {\r\n this.options.onSnapComplete?.(snap)\r\n },\r\n })\r\n }\r\n\r\n // console.timeEnd('scroll')\r\n }\r\n}\r\n","// This file serves as an entry point for the package\r\nimport { Snap } from './src/snap'\r\nglobalThis.Snap = Snap\r\n"],"mappings":"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","names":["debounce","callback","delay","timer","args","context","removeParentSticky","element","addParentSticky","offsetTop","accumulator","top","offsetLeft","left","scrollTop","scrollLeft","SnapElement","align","ignoreSticky","ignoreTransform","width","height","rect","entry","index","uid","Snap","lenis","type","lerp","easing","duration","velocityThreshold","debounceDelay","onSnapStart","onSnapComplete","debounce","element","value","userData","id","uid","options","SnapElement","lastVelocity","velocity","isDecelerating","isTurningBack","scroll","isHorizontal","snaps","rect","align","a","b","prevSnap","distanceToPrevSnap","nextSnap","distanceToNextSnap","snap","distance","Snap"]}