scroll-arrows 0.1.0 → 0.2.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/README.md +141 -14
- package/dist/{chunk-HLZXSGP5.js → chunk-LIT577GH.js} +288 -43
- package/dist/index.cjs +290 -40
- package/dist/index.d.cts +69 -3
- package/dist/index.d.ts +69 -3
- package/dist/index.js +6 -3
- package/dist/react.cjs +310 -40
- package/dist/react.d.cts +22 -5
- package/dist/react.d.ts +22 -5
- package/dist/react.js +25 -2
- package/dist/{types-DehQP2Hx.d.cts → types-Cpvz9wtr.d.cts} +81 -3
- package/dist/{types-DehQP2Hx.d.ts → types-Cpvz9wtr.d.ts} +81 -3
- package/package.json +14 -3
package/dist/react.cjs
CHANGED
|
@@ -25,20 +25,24 @@ function docRect(el) {
|
|
|
25
25
|
height: r2.height
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
function isDegenerateRect(r2) {
|
|
29
|
+
return r2.width <= 0 || r2.height <= 0;
|
|
30
|
+
}
|
|
28
31
|
function center(r2) {
|
|
29
32
|
return { x: r2.left + r2.width / 2, y: r2.top + r2.height / 2 };
|
|
30
33
|
}
|
|
31
|
-
function socketPoint(r2, side) {
|
|
34
|
+
function socketPoint(r2, side, offset = 0) {
|
|
32
35
|
const c = center(r2);
|
|
36
|
+
const o = offset < -0.5 ? -0.5 : offset > 0.5 ? 0.5 : offset;
|
|
33
37
|
switch (side) {
|
|
34
38
|
case "top":
|
|
35
|
-
return { x: c.x, y: r2.top };
|
|
39
|
+
return { x: c.x + o * r2.width, y: r2.top };
|
|
36
40
|
case "bottom":
|
|
37
|
-
return { x: c.x, y: r2.top + r2.height };
|
|
41
|
+
return { x: c.x + o * r2.width, y: r2.top + r2.height };
|
|
38
42
|
case "left":
|
|
39
|
-
return { x: r2.left, y: c.y };
|
|
43
|
+
return { x: r2.left, y: c.y + o * r2.height };
|
|
40
44
|
case "right":
|
|
41
|
-
return { x: r2.left + r2.width, y: c.y };
|
|
45
|
+
return { x: r2.left + r2.width, y: c.y + o * r2.height };
|
|
42
46
|
default:
|
|
43
47
|
return c;
|
|
44
48
|
}
|
|
@@ -71,12 +75,12 @@ function autoSide(self, other) {
|
|
|
71
75
|
}
|
|
72
76
|
return best;
|
|
73
77
|
}
|
|
74
|
-
function resolveEndpoints(startRect, endRect, startSocket, endSocket) {
|
|
78
|
+
function resolveEndpoints(startRect, endRect, startSocket, endSocket, startOffset = 0, endOffset = 0) {
|
|
75
79
|
const s = startSocket === "auto" ? autoSide(startRect, endRect) : startSocket;
|
|
76
80
|
const e = endSocket === "auto" ? autoSide(endRect, startRect) : endSocket;
|
|
77
81
|
return {
|
|
78
|
-
start: socketPoint(startRect, s),
|
|
79
|
-
end: socketPoint(endRect, e),
|
|
82
|
+
start: socketPoint(startRect, s, startOffset),
|
|
83
|
+
end: socketPoint(endRect, e, endOffset),
|
|
80
84
|
startNormal: socketNormal(s),
|
|
81
85
|
endNormal: socketNormal(e)
|
|
82
86
|
};
|
|
@@ -89,10 +93,36 @@ function buildPath(ep, curvature, belly = { x: 0, y: 0 }) {
|
|
|
89
93
|
const reach = dist * (0.3 + curvature * 0.4);
|
|
90
94
|
const sn = startNormal.x || startNormal.y ? startNormal : unit(dx, dy);
|
|
91
95
|
const en = endNormal.x || endNormal.y ? endNormal : unit(-dx, -dy);
|
|
92
|
-
const c1 = {
|
|
93
|
-
|
|
96
|
+
const c1 = {
|
|
97
|
+
x: start.x + sn.x * reach + belly.x,
|
|
98
|
+
y: start.y + sn.y * reach + belly.y
|
|
99
|
+
};
|
|
100
|
+
const c2 = {
|
|
101
|
+
x: end.x + en.x * reach + belly.x,
|
|
102
|
+
y: end.y + en.y * reach + belly.y
|
|
103
|
+
};
|
|
94
104
|
return `M ${r(start.x)} ${r(start.y)} C ${r(c1.x)} ${r(c1.y)} ${r(c2.x)} ${r(c2.y)} ${r(end.x)} ${r(end.y)}`;
|
|
95
105
|
}
|
|
106
|
+
function buildElbowPath(ep) {
|
|
107
|
+
const { start: s, end: e, startNormal: sn, endNormal: en } = ep;
|
|
108
|
+
const dx = e.x - s.x;
|
|
109
|
+
const dy = e.y - s.y;
|
|
110
|
+
const startVertical = sn.y !== 0 || sn.x === 0 && Math.abs(dy) >= Math.abs(dx);
|
|
111
|
+
const endVertical = en.y !== 0 || en.x === 0 && Math.abs(dy) >= Math.abs(dx);
|
|
112
|
+
let pts;
|
|
113
|
+
if (startVertical && endVertical) {
|
|
114
|
+
const midY = (s.y + e.y) / 2;
|
|
115
|
+
pts = [s, { x: s.x, y: midY }, { x: e.x, y: midY }, e];
|
|
116
|
+
} else if (!startVertical && !endVertical) {
|
|
117
|
+
const midX = (s.x + e.x) / 2;
|
|
118
|
+
pts = [s, { x: midX, y: s.y }, { x: midX, y: e.y }, e];
|
|
119
|
+
} else if (startVertical) {
|
|
120
|
+
pts = [s, { x: s.x, y: e.y }, e];
|
|
121
|
+
} else {
|
|
122
|
+
pts = [s, { x: e.x, y: s.y }, e];
|
|
123
|
+
}
|
|
124
|
+
return `M ${r(pts[0].x)} ${r(pts[0].y)}` + pts.slice(1).map((p) => ` L ${r(p.x)} ${r(p.y)}`).join("");
|
|
125
|
+
}
|
|
96
126
|
function routeOffset(start, end, obstacles, padding = 14) {
|
|
97
127
|
const dx = end.x - start.x;
|
|
98
128
|
const dy = end.y - start.y;
|
|
@@ -168,12 +198,29 @@ function easeInOutCubic(t) {
|
|
|
168
198
|
function clamp01(t) {
|
|
169
199
|
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
170
200
|
}
|
|
201
|
+
function prefersReducedMotion() {
|
|
202
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
206
|
+
}
|
|
171
207
|
function scrollProgress(targetRect, range) {
|
|
172
208
|
const vh = window.innerHeight || 1;
|
|
173
209
|
const topFrac = (targetRect.top - window.scrollY) / vh;
|
|
174
210
|
const [enter, leave] = range;
|
|
175
211
|
return clamp01((enter - topFrac) / (enter - leave || 1));
|
|
176
212
|
}
|
|
213
|
+
function staggerWindows(n, stagger) {
|
|
214
|
+
if (n <= 0) return [];
|
|
215
|
+
const s = clamp01(stagger);
|
|
216
|
+
const span = 1 / (1 + (n - 1) * s);
|
|
217
|
+
const step = span * s;
|
|
218
|
+
return Array.from({ length: n }, (_, i) => ({ start: i * step, span }));
|
|
219
|
+
}
|
|
220
|
+
function windowProgress(p, w) {
|
|
221
|
+
if (w.span <= 0) return p > w.start ? 1 : 0;
|
|
222
|
+
return clamp01((p - w.start) / w.span);
|
|
223
|
+
}
|
|
177
224
|
function midpointRect(a, b) {
|
|
178
225
|
const ra = docRect(a);
|
|
179
226
|
const rb = docRect(b);
|
|
@@ -210,7 +257,8 @@ function getOverlay(container) {
|
|
|
210
257
|
(_a = document.body.style).position || (_a.position = "relative");
|
|
211
258
|
} else {
|
|
212
259
|
const pos = getComputedStyle(container).position;
|
|
213
|
-
if (pos === "static")
|
|
260
|
+
if (pos === "static")
|
|
261
|
+
container.style.position = "relative";
|
|
214
262
|
}
|
|
215
263
|
container.appendChild(svg);
|
|
216
264
|
overlays.set(container, svg);
|
|
@@ -330,14 +378,36 @@ var ScrollArrow = class {
|
|
|
330
378
|
this.svg = getOverlay(this.container);
|
|
331
379
|
this.rc = rough__default.default.svg(this.svg);
|
|
332
380
|
this.svg.appendChild(this.group);
|
|
333
|
-
this.
|
|
381
|
+
this.reducedMotion = this.opts.respectReducedMotion !== false && prefersReducedMotion();
|
|
382
|
+
this.progress = this.reducedMotion ? 1 : clamp01(this.opts.progress);
|
|
334
383
|
this.refs = this.resolveRefs();
|
|
335
384
|
this.stroke = options.stroke ?? getComputedStyle(this.container).color ?? "#222";
|
|
336
385
|
this.seed = options.seed ?? deriveSeed(refKey(options.start), refKey(options.end));
|
|
386
|
+
this.enabled = this.opts.enabled ?? true;
|
|
387
|
+
if (!this.enabled) this.group.style.display = "none";
|
|
337
388
|
this.render();
|
|
338
389
|
this.bind();
|
|
339
390
|
this.update();
|
|
340
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Suspend or restore the arrow without tearing it down. Disabling hides it and
|
|
394
|
+
* stops all draw/scroll work; enabling shows it and recomputes geometry (so it
|
|
395
|
+
* reflects any layout change that happened while hidden). Idempotent. Wire it
|
|
396
|
+
* to `matchMedia` to switch arrows off below a breakpoint.
|
|
397
|
+
*/
|
|
398
|
+
setEnabled(on) {
|
|
399
|
+
if (on === this.enabled || this.destroyed) return;
|
|
400
|
+
this.enabled = on;
|
|
401
|
+
if (on) {
|
|
402
|
+
this.group.style.display = "";
|
|
403
|
+
this.render();
|
|
404
|
+
this.update();
|
|
405
|
+
} else {
|
|
406
|
+
this.group.style.display = "none";
|
|
407
|
+
cancelAnimationFrame(this.rafId);
|
|
408
|
+
this.rafId = 0;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
341
411
|
/** Manually set draw progress (0..1). Only meaningful when scroll is false. */
|
|
342
412
|
setProgress(p) {
|
|
343
413
|
this.progress = clamp01(p);
|
|
@@ -351,6 +421,7 @@ var ScrollArrow = class {
|
|
|
351
421
|
destroy() {
|
|
352
422
|
this.destroyed = true;
|
|
353
423
|
this.ro?.disconnect();
|
|
424
|
+
this.teardownReveal();
|
|
354
425
|
window.removeEventListener("scroll", this.onScroll, true);
|
|
355
426
|
window.removeEventListener("resize", this.onScroll);
|
|
356
427
|
cancelAnimationFrame(this.rafId);
|
|
@@ -376,17 +447,30 @@ var ScrollArrow = class {
|
|
|
376
447
|
const list = Array.isArray(a) ? a : [a];
|
|
377
448
|
return list.map(resolve).filter((el) => el !== null);
|
|
378
449
|
}
|
|
379
|
-
computeEndpoints() {
|
|
380
|
-
const sr = docRect(this.refs.start);
|
|
381
|
-
const er = docRect(this.refs.end);
|
|
450
|
+
computeEndpoints(sr, er) {
|
|
382
451
|
const ss = this.opts.startSocket ?? "auto";
|
|
383
452
|
const es = this.opts.endSocket ?? "auto";
|
|
384
|
-
return resolveEndpoints(
|
|
453
|
+
return resolveEndpoints(
|
|
454
|
+
sr,
|
|
455
|
+
er,
|
|
456
|
+
ss,
|
|
457
|
+
es,
|
|
458
|
+
this.opts.startSocketOffset ?? 0,
|
|
459
|
+
this.opts.endSocketOffset ?? 0
|
|
460
|
+
);
|
|
385
461
|
}
|
|
386
462
|
render() {
|
|
387
463
|
while (this.group.firstChild) this.group.removeChild(this.group.firstChild);
|
|
388
464
|
this.segments = [];
|
|
389
|
-
|
|
465
|
+
if (!this.enabled) return;
|
|
466
|
+
const sr = docRect(this.refs.start);
|
|
467
|
+
const er = docRect(this.refs.end);
|
|
468
|
+
if (isDegenerateRect(sr) || isDegenerateRect(er)) {
|
|
469
|
+
this.armReveal();
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
this.teardownReveal();
|
|
473
|
+
const ep = this.computeEndpoints(sr, er);
|
|
390
474
|
const origin = overlayOrigin(this.svg);
|
|
391
475
|
const shift = (e) => ({
|
|
392
476
|
start: { x: e.start.x - origin.x, y: e.start.y - origin.y },
|
|
@@ -403,24 +487,29 @@ var ScrollArrow = class {
|
|
|
403
487
|
this.seed,
|
|
404
488
|
this.opts.anchorEnds ?? true
|
|
405
489
|
);
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
490
|
+
let d;
|
|
491
|
+
if (this.opts.route === "elbow") {
|
|
492
|
+
d = buildElbowPath(local);
|
|
493
|
+
} else {
|
|
494
|
+
const obstacles = this.resolveAvoid().map((el) => {
|
|
495
|
+
const dr = docRect(el);
|
|
496
|
+
return {
|
|
497
|
+
left: dr.left - origin.x,
|
|
498
|
+
top: dr.top - origin.y,
|
|
499
|
+
width: dr.width,
|
|
500
|
+
height: dr.height
|
|
501
|
+
};
|
|
502
|
+
});
|
|
503
|
+
const clear = routeOffset(
|
|
504
|
+
local.start,
|
|
505
|
+
local.end,
|
|
506
|
+
obstacles,
|
|
507
|
+
this.opts.avoidPadding ?? 14
|
|
508
|
+
);
|
|
509
|
+
const BOW = 1.6;
|
|
510
|
+
const belly = { x: clear.x * BOW, y: clear.y * BOW };
|
|
511
|
+
d = buildPath(local, curvature, belly);
|
|
512
|
+
}
|
|
424
513
|
this.appendDrawable(this.rc.path(d, roughOpts), "line");
|
|
425
514
|
const head = this.opts.head;
|
|
426
515
|
const size = this.opts.headSize;
|
|
@@ -466,8 +555,12 @@ var ScrollArrow = class {
|
|
|
466
555
|
let y = pt.y;
|
|
467
556
|
if (offset && total > 0) {
|
|
468
557
|
const eps = Math.min(1, total / 2);
|
|
469
|
-
const before = this.lineEl.getPointAtLength(
|
|
470
|
-
|
|
558
|
+
const before = this.lineEl.getPointAtLength(
|
|
559
|
+
Math.max(0, at * total - eps)
|
|
560
|
+
);
|
|
561
|
+
const after = this.lineEl.getPointAtLength(
|
|
562
|
+
Math.min(total, at * total + eps)
|
|
563
|
+
);
|
|
471
564
|
const n = unitNormal(before, after);
|
|
472
565
|
x += n.x * offset;
|
|
473
566
|
y += n.y * offset;
|
|
@@ -527,19 +620,37 @@ var ScrollArrow = class {
|
|
|
527
620
|
if (this.labelBgEl) this.labelBgEl.style.opacity = String(op);
|
|
528
621
|
}
|
|
529
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Watch the anchors for the moment a hidden one becomes laid out, then redraw.
|
|
625
|
+
* IntersectionObserver fires when a previously `display:none` element gains a
|
|
626
|
+
* box — a transition ResizeObserver does not reliably report. Armed lazily and
|
|
627
|
+
* idempotently; torn down by the next successful render(). Guarded so it no-ops
|
|
628
|
+
* in environments without IntersectionObserver (older engines, some SSR/jsdom).
|
|
629
|
+
*/
|
|
630
|
+
armReveal() {
|
|
631
|
+
if (this.revealObs || this.destroyed) return;
|
|
632
|
+
if (typeof IntersectionObserver === "undefined") return;
|
|
633
|
+
this.revealObs = new IntersectionObserver(() => this.render());
|
|
634
|
+
this.revealObs.observe(this.refs.start);
|
|
635
|
+
this.revealObs.observe(this.refs.end);
|
|
636
|
+
}
|
|
637
|
+
teardownReveal() {
|
|
638
|
+
this.revealObs?.disconnect();
|
|
639
|
+
this.revealObs = void 0;
|
|
640
|
+
}
|
|
530
641
|
bind() {
|
|
531
642
|
const targets = [this.refs.start, this.refs.end, ...this.resolveAvoid()];
|
|
532
643
|
if (this.refs.target) targets.push(this.refs.target);
|
|
533
644
|
this.ro = new ResizeObserver(() => this.render());
|
|
534
645
|
targets.forEach((t) => this.ro.observe(t));
|
|
535
|
-
if (this.opts.scroll !== false) {
|
|
646
|
+
if (this.opts.scroll !== false && !this.reducedMotion) {
|
|
536
647
|
window.addEventListener("scroll", this.onScroll, true);
|
|
537
648
|
window.addEventListener("resize", this.onScroll);
|
|
538
649
|
}
|
|
539
650
|
}
|
|
540
651
|
update() {
|
|
541
|
-
if (this.destroyed) return;
|
|
542
|
-
if (this.opts.scroll === false) {
|
|
652
|
+
if (this.destroyed || !this.enabled) return;
|
|
653
|
+
if (this.opts.scroll === false || this.reducedMotion) {
|
|
543
654
|
this.applyProgress();
|
|
544
655
|
return;
|
|
545
656
|
}
|
|
@@ -561,6 +672,140 @@ function clampAt(t) {
|
|
|
561
672
|
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
562
673
|
}
|
|
563
674
|
|
|
675
|
+
// src/group.ts
|
|
676
|
+
var ScrollArrowGroup = class {
|
|
677
|
+
constructor(options) {
|
|
678
|
+
this.elements = [];
|
|
679
|
+
this.target = null;
|
|
680
|
+
this.progress = 0;
|
|
681
|
+
this.rafId = 0;
|
|
682
|
+
this.destroyed = false;
|
|
683
|
+
this.onScroll = () => {
|
|
684
|
+
if (this.rafId) return;
|
|
685
|
+
this.rafId = requestAnimationFrame(() => {
|
|
686
|
+
this.rafId = 0;
|
|
687
|
+
this.update();
|
|
688
|
+
});
|
|
689
|
+
};
|
|
690
|
+
if (!options.arrows || options.arrows.length === 0) {
|
|
691
|
+
throw new Error("[scroll-arrows] group needs at least one arrow");
|
|
692
|
+
}
|
|
693
|
+
this.opts = {
|
|
694
|
+
stagger: 1,
|
|
695
|
+
speed: 1,
|
|
696
|
+
easing: (t) => t,
|
|
697
|
+
...options
|
|
698
|
+
};
|
|
699
|
+
this.reducedMotion = this.opts.respectReducedMotion !== false && prefersReducedMotion();
|
|
700
|
+
this.enabled = this.opts.enabled ?? true;
|
|
701
|
+
this.arrows = options.arrows.map(
|
|
702
|
+
(a) => new ScrollArrow({
|
|
703
|
+
...a,
|
|
704
|
+
scroll: false,
|
|
705
|
+
progress: 0,
|
|
706
|
+
respectReducedMotion: false,
|
|
707
|
+
enabled: this.enabled
|
|
708
|
+
})
|
|
709
|
+
);
|
|
710
|
+
this.windows = staggerWindows(this.arrows.length, this.opts.stagger);
|
|
711
|
+
for (const a of options.arrows) {
|
|
712
|
+
const s = resolve2(a.start);
|
|
713
|
+
const e = resolve2(a.end);
|
|
714
|
+
if (s) this.elements.push(s);
|
|
715
|
+
if (e) this.elements.push(e);
|
|
716
|
+
}
|
|
717
|
+
const scroll = this.opts.scroll;
|
|
718
|
+
if (scroll !== false && scroll?.target) {
|
|
719
|
+
this.target = resolve2(scroll.target);
|
|
720
|
+
}
|
|
721
|
+
if (this.reducedMotion) this.progress = 1;
|
|
722
|
+
this.bind();
|
|
723
|
+
this.update();
|
|
724
|
+
}
|
|
725
|
+
/** Manually set group progress (0..1). Only meaningful when scroll is false. */
|
|
726
|
+
setProgress(p) {
|
|
727
|
+
this.progress = clamp01(p);
|
|
728
|
+
this.distribute();
|
|
729
|
+
}
|
|
730
|
+
/** Recompute geometry for every arrow (call after layout changes). */
|
|
731
|
+
refresh() {
|
|
732
|
+
this.arrows.forEach((a) => a.refresh());
|
|
733
|
+
this.update();
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Suspend or restore the whole group without tearing it down. Hides every
|
|
737
|
+
* arrow and stops scroll work when off; restores and recomputes when on.
|
|
738
|
+
* Idempotent. Wire to `matchMedia` for breakpoint-aware diagrams.
|
|
739
|
+
*/
|
|
740
|
+
setEnabled(on) {
|
|
741
|
+
if (on === this.enabled || this.destroyed) return;
|
|
742
|
+
this.enabled = on;
|
|
743
|
+
this.arrows.forEach((a) => a.setEnabled(on));
|
|
744
|
+
if (on) {
|
|
745
|
+
this.bind();
|
|
746
|
+
this.update();
|
|
747
|
+
} else {
|
|
748
|
+
window.removeEventListener("scroll", this.onScroll, true);
|
|
749
|
+
window.removeEventListener("resize", this.onScroll);
|
|
750
|
+
cancelAnimationFrame(this.rafId);
|
|
751
|
+
this.rafId = 0;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
destroy() {
|
|
755
|
+
this.destroyed = true;
|
|
756
|
+
window.removeEventListener("scroll", this.onScroll, true);
|
|
757
|
+
window.removeEventListener("resize", this.onScroll);
|
|
758
|
+
cancelAnimationFrame(this.rafId);
|
|
759
|
+
this.arrows.forEach((a) => a.destroy());
|
|
760
|
+
}
|
|
761
|
+
// --- internals ---------------------------------------------------------
|
|
762
|
+
bind() {
|
|
763
|
+
if (this.opts.scroll === false || this.reducedMotion || !this.enabled)
|
|
764
|
+
return;
|
|
765
|
+
window.addEventListener("scroll", this.onScroll, true);
|
|
766
|
+
window.addEventListener("resize", this.onScroll);
|
|
767
|
+
}
|
|
768
|
+
update() {
|
|
769
|
+
if (this.destroyed || !this.enabled) return;
|
|
770
|
+
if (this.opts.scroll === false || this.reducedMotion) {
|
|
771
|
+
this.distribute();
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const scroll = this.opts.scroll ?? {};
|
|
775
|
+
const range = scroll.range ?? [0.85, 0.35];
|
|
776
|
+
const rect = this.target ? docRect(this.target) : this.groupRect();
|
|
777
|
+
const raw = scrollProgress(rect, range);
|
|
778
|
+
this.progress = clamp01(raw * this.opts.speed);
|
|
779
|
+
this.distribute();
|
|
780
|
+
}
|
|
781
|
+
/** Push each arrow to its sliced local progress for the current group value. */
|
|
782
|
+
distribute() {
|
|
783
|
+
const eased = clamp01(this.opts.easing(this.progress));
|
|
784
|
+
this.arrows.forEach((arrow, i) => {
|
|
785
|
+
arrow.setProgress(windowProgress(eased, this.windows[i]));
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
/** Synthetic rect spanning every endpoint, used as the default trigger. */
|
|
789
|
+
groupRect() {
|
|
790
|
+
const rects = this.elements.map(docRect);
|
|
791
|
+
if (rects.length === 0) return { left: 0, top: 0, width: 0, height: 0 };
|
|
792
|
+
let left = Infinity;
|
|
793
|
+
let top = Infinity;
|
|
794
|
+
let right = -Infinity;
|
|
795
|
+
let bottom = -Infinity;
|
|
796
|
+
for (const r2 of rects) {
|
|
797
|
+
left = Math.min(left, r2.left);
|
|
798
|
+
top = Math.min(top, r2.top);
|
|
799
|
+
right = Math.max(right, r2.left + r2.width);
|
|
800
|
+
bottom = Math.max(bottom, r2.top + r2.height);
|
|
801
|
+
}
|
|
802
|
+
return { left, top, width: right - left, height: bottom - top };
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
function resolve2(ref) {
|
|
806
|
+
return typeof ref === "string" ? document.querySelector(ref) : ref;
|
|
807
|
+
}
|
|
808
|
+
|
|
564
809
|
// src/react.tsx
|
|
565
810
|
function read(a) {
|
|
566
811
|
if (typeof a === "string") return a;
|
|
@@ -586,8 +831,33 @@ function ScrollArrowLine(props) {
|
|
|
586
831
|
useScrollArrow(props);
|
|
587
832
|
return null;
|
|
588
833
|
}
|
|
834
|
+
function useScrollArrowGroup(options) {
|
|
835
|
+
const groupRef = react.useRef(null);
|
|
836
|
+
const { arrows, deps = [], ...rest } = options;
|
|
837
|
+
react.useEffect(() => {
|
|
838
|
+
const resolved = arrows.map((a) => {
|
|
839
|
+
const s = read(a.start);
|
|
840
|
+
const e = read(a.end);
|
|
841
|
+
if (!s || !e) return null;
|
|
842
|
+
return { ...a, start: s, end: e };
|
|
843
|
+
}).filter((a) => a !== null);
|
|
844
|
+
if (resolved.length === 0) return;
|
|
845
|
+
const group = new ScrollArrowGroup({ ...rest, arrows: resolved });
|
|
846
|
+
groupRef.current = group;
|
|
847
|
+
return () => {
|
|
848
|
+
group.destroy();
|
|
849
|
+
groupRef.current = null;
|
|
850
|
+
};
|
|
851
|
+
}, deps);
|
|
852
|
+
}
|
|
853
|
+
function ScrollArrowGroupLines(props) {
|
|
854
|
+
useScrollArrowGroup(props);
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
589
857
|
|
|
858
|
+
exports.ScrollArrowGroupLines = ScrollArrowGroupLines;
|
|
590
859
|
exports.ScrollArrowLine = ScrollArrowLine;
|
|
591
860
|
exports.useScrollArrow = useScrollArrow;
|
|
861
|
+
exports.useScrollArrowGroup = useScrollArrowGroup;
|
|
592
862
|
//# sourceMappingURL=react.cjs.map
|
|
593
863
|
//# sourceMappingURL=react.cjs.map
|
package/dist/react.d.cts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { RefObject } from 'react';
|
|
2
|
-
import { S as ScrollArrowOptions } from './types-
|
|
2
|
+
import { S as ScrollArrowOptions, a as ScrollArrowGroupOptions } from './types-Cpvz9wtr.cjs';
|
|
3
3
|
|
|
4
4
|
type Anchor = RefObject<Element | null> | Element | string;
|
|
5
|
-
interface UseScrollArrowOptions extends Omit<ScrollArrowOptions,
|
|
5
|
+
interface UseScrollArrowOptions extends Omit<ScrollArrowOptions, 'start' | 'end'> {
|
|
6
6
|
start: Anchor;
|
|
7
7
|
end: Anchor;
|
|
8
8
|
/** Re-create the arrow when any value here changes. */
|
|
@@ -13,9 +13,26 @@ interface UseScrollArrowOptions extends Omit<ScrollArrowOptions, "start" | "end"
|
|
|
13
13
|
* the arrow lives in an overlay <svg>, not the React tree.
|
|
14
14
|
*/
|
|
15
15
|
declare function useScrollArrow(options: UseScrollArrowOptions): void;
|
|
16
|
-
|
|
17
|
-
}
|
|
16
|
+
type ScrollArrowProps = UseScrollArrowOptions;
|
|
18
17
|
/** Declarative component form. Renders nothing itself. */
|
|
19
18
|
declare function ScrollArrowLine(props: ScrollArrowProps): null;
|
|
19
|
+
/** One arrow in a group, with React-ref anchors. */
|
|
20
|
+
interface GroupArrow extends Omit<ScrollArrowOptions, 'start' | 'end'> {
|
|
21
|
+
start: Anchor;
|
|
22
|
+
end: Anchor;
|
|
23
|
+
}
|
|
24
|
+
interface UseScrollArrowGroupOptions extends Omit<ScrollArrowGroupOptions, 'arrows'> {
|
|
25
|
+
arrows: GroupArrow[];
|
|
26
|
+
/** Re-create the group when any value here changes. */
|
|
27
|
+
deps?: unknown[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Imperatively manage a staggered ScrollArrowGroup tied to element refs.
|
|
31
|
+
* Returns nothing; the arrows live in an overlay <svg>, not the React tree.
|
|
32
|
+
*/
|
|
33
|
+
declare function useScrollArrowGroup(options: UseScrollArrowGroupOptions): void;
|
|
34
|
+
type ScrollArrowGroupProps = UseScrollArrowGroupOptions;
|
|
35
|
+
/** Declarative group form. Renders nothing itself. */
|
|
36
|
+
declare function ScrollArrowGroupLines(props: ScrollArrowGroupProps): null;
|
|
20
37
|
|
|
21
|
-
export { ScrollArrowLine, ScrollArrowOptions, type ScrollArrowProps, type UseScrollArrowOptions, useScrollArrow };
|
|
38
|
+
export { type GroupArrow, ScrollArrowGroupLines, ScrollArrowGroupOptions, type ScrollArrowGroupProps, ScrollArrowLine, ScrollArrowOptions, type ScrollArrowProps, type UseScrollArrowGroupOptions, type UseScrollArrowOptions, useScrollArrow, useScrollArrowGroup };
|
package/dist/react.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { RefObject } from 'react';
|
|
2
|
-
import { S as ScrollArrowOptions } from './types-
|
|
2
|
+
import { S as ScrollArrowOptions, a as ScrollArrowGroupOptions } from './types-Cpvz9wtr.js';
|
|
3
3
|
|
|
4
4
|
type Anchor = RefObject<Element | null> | Element | string;
|
|
5
|
-
interface UseScrollArrowOptions extends Omit<ScrollArrowOptions,
|
|
5
|
+
interface UseScrollArrowOptions extends Omit<ScrollArrowOptions, 'start' | 'end'> {
|
|
6
6
|
start: Anchor;
|
|
7
7
|
end: Anchor;
|
|
8
8
|
/** Re-create the arrow when any value here changes. */
|
|
@@ -13,9 +13,26 @@ interface UseScrollArrowOptions extends Omit<ScrollArrowOptions, "start" | "end"
|
|
|
13
13
|
* the arrow lives in an overlay <svg>, not the React tree.
|
|
14
14
|
*/
|
|
15
15
|
declare function useScrollArrow(options: UseScrollArrowOptions): void;
|
|
16
|
-
|
|
17
|
-
}
|
|
16
|
+
type ScrollArrowProps = UseScrollArrowOptions;
|
|
18
17
|
/** Declarative component form. Renders nothing itself. */
|
|
19
18
|
declare function ScrollArrowLine(props: ScrollArrowProps): null;
|
|
19
|
+
/** One arrow in a group, with React-ref anchors. */
|
|
20
|
+
interface GroupArrow extends Omit<ScrollArrowOptions, 'start' | 'end'> {
|
|
21
|
+
start: Anchor;
|
|
22
|
+
end: Anchor;
|
|
23
|
+
}
|
|
24
|
+
interface UseScrollArrowGroupOptions extends Omit<ScrollArrowGroupOptions, 'arrows'> {
|
|
25
|
+
arrows: GroupArrow[];
|
|
26
|
+
/** Re-create the group when any value here changes. */
|
|
27
|
+
deps?: unknown[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Imperatively manage a staggered ScrollArrowGroup tied to element refs.
|
|
31
|
+
* Returns nothing; the arrows live in an overlay <svg>, not the React tree.
|
|
32
|
+
*/
|
|
33
|
+
declare function useScrollArrowGroup(options: UseScrollArrowGroupOptions): void;
|
|
34
|
+
type ScrollArrowGroupProps = UseScrollArrowGroupOptions;
|
|
35
|
+
/** Declarative group form. Renders nothing itself. */
|
|
36
|
+
declare function ScrollArrowGroupLines(props: ScrollArrowGroupProps): null;
|
|
20
37
|
|
|
21
|
-
export { ScrollArrowLine, ScrollArrowOptions, type ScrollArrowProps, type UseScrollArrowOptions, useScrollArrow };
|
|
38
|
+
export { type GroupArrow, ScrollArrowGroupLines, ScrollArrowGroupOptions, type ScrollArrowGroupProps, ScrollArrowLine, ScrollArrowOptions, type ScrollArrowProps, type UseScrollArrowGroupOptions, type UseScrollArrowOptions, useScrollArrow, useScrollArrowGroup };
|
package/dist/react.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ScrollArrow } from './chunk-
|
|
1
|
+
import { ScrollArrow, ScrollArrowGroup } from './chunk-LIT577GH.js';
|
|
2
2
|
import { useRef, useEffect } from 'react';
|
|
3
3
|
|
|
4
4
|
function read(a) {
|
|
@@ -25,7 +25,30 @@ function ScrollArrowLine(props) {
|
|
|
25
25
|
useScrollArrow(props);
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
|
+
function useScrollArrowGroup(options) {
|
|
29
|
+
const groupRef = useRef(null);
|
|
30
|
+
const { arrows, deps = [], ...rest } = options;
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const resolved = arrows.map((a) => {
|
|
33
|
+
const s = read(a.start);
|
|
34
|
+
const e = read(a.end);
|
|
35
|
+
if (!s || !e) return null;
|
|
36
|
+
return { ...a, start: s, end: e };
|
|
37
|
+
}).filter((a) => a !== null);
|
|
38
|
+
if (resolved.length === 0) return;
|
|
39
|
+
const group = new ScrollArrowGroup({ ...rest, arrows: resolved });
|
|
40
|
+
groupRef.current = group;
|
|
41
|
+
return () => {
|
|
42
|
+
group.destroy();
|
|
43
|
+
groupRef.current = null;
|
|
44
|
+
};
|
|
45
|
+
}, deps);
|
|
46
|
+
}
|
|
47
|
+
function ScrollArrowGroupLines(props) {
|
|
48
|
+
useScrollArrowGroup(props);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
28
51
|
|
|
29
|
-
export { ScrollArrowLine, useScrollArrow };
|
|
52
|
+
export { ScrollArrowGroupLines, ScrollArrowLine, useScrollArrow, useScrollArrowGroup };
|
|
30
53
|
//# sourceMappingURL=react.js.map
|
|
31
54
|
//# sourceMappingURL=react.js.map
|