coonlink-luxy 26.1.1 → 26.1.3

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/index.d.ts CHANGED
@@ -5,13 +5,12 @@ export type CoonlinkLuxyOptions = {
5
5
  targetSpeed?: number;
6
6
  targetPercentage?: number;
7
7
  respectReducedMotion?: boolean;
8
+ respectOpenModalOverlay?: boolean;
8
9
  };
9
10
  export interface CoonlinkLuxyInstance {
10
11
  init(options?: CoonlinkLuxyOptions): boolean;
11
12
  destroy(): void;
12
13
  }
13
- declare const defaults: Required<CoonlinkLuxyOptions>;
14
14
  export declare function createCoonlinkLuxy(): CoonlinkLuxyInstance;
15
- export declare function getCoonlinkLuxyDefaults(): Readonly<typeof defaults>;
16
- export {};
15
+ export declare function getCoonlinkLuxyDefaults(): Readonly<Required<CoonlinkLuxyOptions>>;
17
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAA;IAC5C,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,QAAA,MAAM,QAAQ,EAAE,QAAQ,CAAC,mBAAmB,CAO3C,CAAA;AAqKD,wBAAgB,kBAAkB,IAAI,oBAAoB,CAEzD;AAED,wBAAgB,uBAAuB,IAAI,QAAQ,CAAC,OAAO,QAAQ,CAAC,CAEnE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,CAAC;AAEF,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC;IAC7C,OAAO,IAAI,IAAI,CAAC;CACjB;AA4ND,wBAAgB,kBAAkB,IAAI,oBAAoB,CAEzD;AAED,wBAAgB,uBAAuB,IAAI,QAAQ,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAEjF"}
package/dist/index.js CHANGED
@@ -1,3 +1,14 @@
1
+ /*
2
+ ✨ CoonDev • https://dev.coonlink.com/
3
+
4
+ ▄█▄ ████▄ ████▄ ▄ ██▄ ▄███▄ ▄
5
+ █▀ ▀▄ █ █ █ █ █ █ █ █▀ ▀ █
6
+ █ ▀ █ █ █ █ ██ █ █ █ ██▄▄ █ █
7
+ █▄ ▄▀ ▀████ ▀████ █ █ █ █ █ █▄ ▄▀ █ █
8
+ ▀███▀ █ █ █ ███▀ ▀███▀ █ █
9
+ █ ██ █▐
10
+
11
+ */
1
12
  const defaults = {
2
13
  wrapper: '#luxy',
3
14
  targets: '.luxy-el',
@@ -5,18 +16,24 @@ const defaults = {
5
16
  targetSpeed: 0.02,
6
17
  targetPercentage: 0.1,
7
18
  respectReducedMotion: true,
19
+ respectOpenModalOverlay: true,
8
20
  };
21
+ const SETTLE_EPS = 0.12;
22
+ function hasOpenModalOverlay() {
23
+ return !!document.querySelector('[data-slot="dialog-overlay"][data-state="open"],' +
24
+ '[data-slot="alert-dialog-overlay"][data-state="open"],' +
25
+ '[data-slot="sheet-overlay"][data-state="open"]');
26
+ }
9
27
  function extend(base, over) {
10
- return { ...base, ...(over ?? {}) };
28
+ return { ...base, ...(over !== null && over !== void 0 ? over : {}) };
11
29
  }
12
30
  function getScrollTop() {
13
- return (window.scrollY ??
14
- document.documentElement.scrollTop ??
15
- document.body.scrollTop ??
16
- 0);
31
+ var _a, _b, _c;
32
+ return ((_c = (_b = (_a = window.scrollY) !== null && _a !== void 0 ? _a : document.documentElement.scrollTop) !== null && _b !== void 0 ? _b : document.body.scrollTop) !== null && _c !== void 0 ? _c : 0);
17
33
  }
18
34
  function readSpeedY(el) {
19
- return el.getAttribute('data-speed-y') ?? el.getAttribute('data-speed-Y');
35
+ var _a;
36
+ return (_a = el.getAttribute('data-speed-y')) !== null && _a !== void 0 ? _a : el.getAttribute('data-speed-Y');
20
37
  }
21
38
  class Engine {
22
39
  constructor() {
@@ -27,25 +44,114 @@ class Engine {
27
44
  this.scrollTop = 0;
28
45
  this.scrollRaf = 0;
29
46
  this.resizeObserver = null;
47
+ this.pauseForOverlay = false;
48
+ this.overlayObserver = null;
49
+ this.onScrollResume = () => { this.ensureTicking(); };
30
50
  this.onResize = () => {
31
51
  if (!this.wrapper)
32
52
  return;
33
53
  const h = Math.max(this.wrapper.scrollHeight, this.wrapper.clientHeight);
34
54
  document.body.style.height = `${h}px`;
55
+ this.ensureTicking();
56
+ };
57
+ }
58
+ isSettled() {
59
+ if (Math.abs(this.scrollTop - this.wrapperOffset) > SETTLE_EPS)
60
+ return false;
61
+ const ts = this.settings.targetSpeed;
62
+ const st = this.scrollTop;
63
+ for (let i = 0; i < this.targets.length; i++) {
64
+ const t = this.targets[i];
65
+ const idealY = st * Number(ts) * Number(t.speedY);
66
+ if (Math.abs(idealY - t.top) > SETTLE_EPS)
67
+ return false;
68
+ if (t.horizontal) {
69
+ const idealX = st * Number(ts) * Number(t.speedX);
70
+ if (Math.abs(idealX - t.left) > SETTLE_EPS)
71
+ return false;
72
+ }
73
+ }
74
+ return true;
75
+ }
76
+ stopScrollLoop() {
77
+ window.removeEventListener('scroll', this.onScrollResume);
78
+ if (this.scrollRaf) {
79
+ window.cancelAnimationFrame(this.scrollRaf);
80
+ this.scrollRaf = 0;
81
+ }
82
+ }
83
+ ensureTicking() {
84
+ if (this.pauseForOverlay || !this.wrapper)
85
+ return;
86
+ window.removeEventListener('scroll', this.onScrollResume);
87
+ if (this.scrollRaf !== 0)
88
+ return;
89
+ const tick = () => {
90
+ if (this.pauseForOverlay || !this.wrapper) {
91
+ this.scrollRaf = 0;
92
+ return;
93
+ }
94
+ this.scrollTop = getScrollTop();
95
+ this.wrapperUpdate();
96
+ for (let i = 0; i < this.targets.length; i++)
97
+ this.targetsUpdate(this.targets[i]);
98
+ if (this.isSettled()) {
99
+ this.scrollRaf = 0;
100
+ window.addEventListener('scroll', this.onScrollResume, { passive: true });
101
+ return;
102
+ }
103
+ this.scrollRaf = window.requestAnimationFrame(tick);
35
104
  };
105
+ this.scrollRaf = window.requestAnimationFrame(tick);
106
+ }
107
+ syncOverlayPause() {
108
+ if (!this.settings.respectOpenModalOverlay) {
109
+ this.pauseForOverlay = false;
110
+ return;
111
+ }
112
+ const blocked = hasOpenModalOverlay();
113
+ if (blocked === this.pauseForOverlay)
114
+ return;
115
+ this.pauseForOverlay = blocked;
116
+ if (blocked) {
117
+ this.stopScrollLoop();
118
+ }
119
+ else {
120
+ this.onResize();
121
+ }
122
+ }
123
+ attachOverlayWatcher() {
124
+ this.detachOverlayWatcher();
125
+ if (!this.settings.respectOpenModalOverlay)
126
+ return;
127
+ this.pauseForOverlay = hasOpenModalOverlay();
128
+ this.overlayObserver = new MutationObserver(() => {
129
+ this.syncOverlayPause();
130
+ if (!this.pauseForOverlay)
131
+ this.ensureTicking();
132
+ });
133
+ this.overlayObserver.observe(document.body, {
134
+ childList: true, subtree: true, attributes: true, attributeFilter: ['data-state'],
135
+ });
136
+ }
137
+ detachOverlayWatcher() {
138
+ var _a;
139
+ (_a = this.overlayObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
140
+ this.overlayObserver = null;
141
+ this.pauseForOverlay = false;
36
142
  }
37
143
  init(options) {
144
+ var _a;
38
145
  this.destroy();
39
146
  this.settings = extend(defaults, options);
40
147
  if (this.settings.respectReducedMotion) {
41
- const mq = window.matchMedia?.('(prefers-reduced-motion: reduce)');
42
- if (mq?.matches)
148
+ const mq = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, '(prefers-reduced-motion: reduce)');
149
+ if (mq === null || mq === void 0 ? void 0 : mq.matches)
43
150
  return false;
44
151
  }
45
152
  const el = document.querySelector(this.settings.wrapper);
46
- if (!el || !(el instanceof HTMLElement)) {
153
+ if (!el || !(el instanceof HTMLElement))
47
154
  return false;
48
- }
49
155
  this.wrapper = el;
50
156
  const nodeList = document.querySelectorAll(this.settings.targets);
51
157
  const scrollHeight = Math.max(this.wrapper.scrollHeight, this.wrapper.clientHeight);
@@ -66,8 +172,7 @@ class Engine {
66
172
  elm: node,
67
173
  offset: offset ? parseInt(offset, 10) || 0 : 0,
68
174
  horizontal: horizontal ? 1 : 0,
69
- top: 0,
70
- left: 0,
175
+ top: 0, left: 0,
71
176
  speedX: speedX ? Number(speedX) || 1 : 1,
72
177
  speedY: speedY ? Number(speedY) || 1 : 1,
73
178
  percentage: percentage ? parseInt(percentage, 10) || 0 : 0,
@@ -76,61 +181,48 @@ class Engine {
76
181
  this.resizeObserver = new ResizeObserver(this.onResize);
77
182
  this.resizeObserver.observe(this.wrapper);
78
183
  window.addEventListener('resize', this.onResize);
79
- const tickScroll = () => {
80
- this.scrollTop = getScrollTop();
81
- this.wrapperUpdate();
82
- for (let i = 0; i < this.targets.length; i++) {
83
- this.targetsUpdate(this.targets[i]);
84
- }
85
- this.scrollRaf = window.requestAnimationFrame(tickScroll);
86
- };
87
- this.scrollRaf = window.requestAnimationFrame(tickScroll);
184
+ this.attachOverlayWatcher();
185
+ if (!this.pauseForOverlay)
186
+ this.ensureTicking();
88
187
  return true;
89
188
  }
90
189
  wrapperUpdate() {
91
190
  if (!this.wrapper)
92
191
  return;
93
- this.wrapperOffset +=
94
- (this.scrollTop - this.wrapperOffset) * this.settings.wrapperSpeed;
192
+ this.wrapperOffset += (this.scrollTop - this.wrapperOffset) * this.settings.wrapperSpeed;
193
+ const maxOffset = Math.max(0, document.body.scrollHeight - window.innerHeight);
194
+ this.wrapperOffset = Math.max(0, Math.min(this.wrapperOffset, maxOffset));
95
195
  const y = Math.round(-this.wrapperOffset * 100) / 100;
96
196
  this.wrapper.style.transform = `translate3d(0, ${y}px, 0)`;
97
197
  }
98
198
  targetsUpdate(target) {
99
199
  const ts = this.settings.targetSpeed;
100
200
  const tp = this.settings.targetPercentage;
101
- target.top +=
102
- (this.scrollTop * Number(ts) * Number(target.speedY) - target.top) * tp;
103
- target.left +=
104
- (this.scrollTop * Number(ts) * Number(target.speedX) - target.left) * tp;
105
- const targetOffsetTop = parseInt(String(target.percentage), 10) -
106
- target.top -
107
- parseInt(String(target.offset), 10);
201
+ target.top += (this.scrollTop * Number(ts) * Number(target.speedY) - target.top) * tp;
202
+ target.left += (this.scrollTop * Number(ts) * Number(target.speedX) - target.left) * tp;
203
+ const targetOffsetTop = parseInt(String(target.percentage), 10) - target.top - parseInt(String(target.offset), 10);
108
204
  let offsetY = Math.round(targetOffsetTop * -100) / 100;
109
205
  let offsetX = 0;
110
206
  if (target.horizontal) {
111
- const targetOffsetLeft = parseInt(String(target.percentage), 10) -
112
- target.left -
113
- parseInt(String(target.offset), 10);
207
+ const targetOffsetLeft = parseInt(String(target.percentage), 10) - target.left - parseInt(String(target.offset), 10);
114
208
  offsetX = Math.round(targetOffsetLeft * -100) / 100;
115
209
  }
116
210
  target.elm.style.transform = `translate3d(${offsetX}px, ${offsetY}px, 0)`;
117
211
  }
118
212
  destroy() {
213
+ var _a;
214
+ this.detachOverlayWatcher();
215
+ this.stopScrollLoop();
119
216
  window.removeEventListener('resize', this.onResize);
120
- this.resizeObserver?.disconnect();
217
+ (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
121
218
  this.resizeObserver = null;
122
- if (this.scrollRaf) {
123
- window.cancelAnimationFrame(this.scrollRaf);
124
- this.scrollRaf = 0;
125
- }
126
219
  document.body.style.height = '';
127
220
  if (this.wrapper) {
128
221
  this.wrapper.removeAttribute('style');
129
222
  this.wrapper = null;
130
223
  }
131
- for (let i = 0; i < this.targets.length; i++) {
224
+ for (let i = 0; i < this.targets.length; i++)
132
225
  this.targets[i].elm.removeAttribute('style');
133
- }
134
226
  this.targets = [];
135
227
  this.wrapperOffset = 0;
136
228
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coonlink-luxy",
3
- "version": "26.1.1",
3
+ "version": "26.1.3",
4
4
  "description": "Inertia scroll and parallax (luxy-style) with fixes for modern browsers, React/Next, and prefers-reduced-motion.",
5
5
  "homepage": "https://dev.coonlink.com",
6
6
  "keywords": [
package/README.md DELETED
@@ -1,36 +0,0 @@
1
- # coonlink-luxy
2
-
3
- Inertia scroll and parallax (same idea as [luxy.js](https://github.com/min30327/luxy.js)), with fixes for modern browsers, `data-speed-y`, and clean `destroy()` for React/Next.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm i coonlink-luxy
9
- ```
10
-
11
- ## Markup
12
-
13
- ```html
14
- <div id="luxy">
15
- <div class="luxy-el" data-speed-y="5" data-offset="-50">…</div>
16
- </div>
17
- ```
18
-
19
- ## Usage
20
-
21
- ```ts
22
- import { createCoonlinkLuxy } from 'coonlink-luxy'
23
-
24
- const luxy = createCoonlinkLuxy()
25
- luxy.init({
26
- wrapper: '#luxy',
27
- targets: '.luxy-el',
28
- wrapperSpeed: 0.08,
29
- targetSpeed: 0.02,
30
- targetPercentage: 0.1,
31
- respectReducedMotion: true,
32
- })
33
-
34
- // SPA / React unmount:
35
- luxy.destroy()
36
- ```