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