scroll-arrows 0.1.3 → 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 +118 -2
- package/dist/{chunk-GKWBGFLA.js → chunk-LIT577GH.js} +272 -38
- package/dist/index.cjs +274 -35
- package/dist/index.d.cts +69 -3
- package/dist/index.d.ts +69 -3
- package/dist/index.js +6 -3
- package/dist/react.cjs +294 -35
- package/dist/react.d.cts +20 -2
- package/dist/react.d.ts +20 -2
- package/dist/react.js +25 -2
- package/dist/{types-CDd8JqZX.d.cts → types-Cpvz9wtr.d.cts} +79 -1
- package/dist/{types-CDd8JqZX.d.ts → types-Cpvz9wtr.d.ts} +79 -1
- package/package.json +1 -1
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
|
};
|
|
@@ -99,6 +103,26 @@ function buildPath(ep, curvature, belly = { x: 0, y: 0 }) {
|
|
|
99
103
|
};
|
|
100
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)}`;
|
|
101
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
|
+
}
|
|
102
126
|
function routeOffset(start, end, obstacles, padding = 14) {
|
|
103
127
|
const dx = end.x - start.x;
|
|
104
128
|
const dy = end.y - start.y;
|
|
@@ -174,12 +198,29 @@ function easeInOutCubic(t) {
|
|
|
174
198
|
function clamp01(t) {
|
|
175
199
|
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
176
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
|
+
}
|
|
177
207
|
function scrollProgress(targetRect, range) {
|
|
178
208
|
const vh = window.innerHeight || 1;
|
|
179
209
|
const topFrac = (targetRect.top - window.scrollY) / vh;
|
|
180
210
|
const [enter, leave] = range;
|
|
181
211
|
return clamp01((enter - topFrac) / (enter - leave || 1));
|
|
182
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
|
+
}
|
|
183
224
|
function midpointRect(a, b) {
|
|
184
225
|
const ra = docRect(a);
|
|
185
226
|
const rb = docRect(b);
|
|
@@ -337,14 +378,36 @@ var ScrollArrow = class {
|
|
|
337
378
|
this.svg = getOverlay(this.container);
|
|
338
379
|
this.rc = rough__default.default.svg(this.svg);
|
|
339
380
|
this.svg.appendChild(this.group);
|
|
340
|
-
this.
|
|
381
|
+
this.reducedMotion = this.opts.respectReducedMotion !== false && prefersReducedMotion();
|
|
382
|
+
this.progress = this.reducedMotion ? 1 : clamp01(this.opts.progress);
|
|
341
383
|
this.refs = this.resolveRefs();
|
|
342
384
|
this.stroke = options.stroke ?? getComputedStyle(this.container).color ?? "#222";
|
|
343
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";
|
|
344
388
|
this.render();
|
|
345
389
|
this.bind();
|
|
346
390
|
this.update();
|
|
347
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
|
+
}
|
|
348
411
|
/** Manually set draw progress (0..1). Only meaningful when scroll is false. */
|
|
349
412
|
setProgress(p) {
|
|
350
413
|
this.progress = clamp01(p);
|
|
@@ -358,6 +421,7 @@ var ScrollArrow = class {
|
|
|
358
421
|
destroy() {
|
|
359
422
|
this.destroyed = true;
|
|
360
423
|
this.ro?.disconnect();
|
|
424
|
+
this.teardownReveal();
|
|
361
425
|
window.removeEventListener("scroll", this.onScroll, true);
|
|
362
426
|
window.removeEventListener("resize", this.onScroll);
|
|
363
427
|
cancelAnimationFrame(this.rafId);
|
|
@@ -383,17 +447,30 @@ var ScrollArrow = class {
|
|
|
383
447
|
const list = Array.isArray(a) ? a : [a];
|
|
384
448
|
return list.map(resolve).filter((el) => el !== null);
|
|
385
449
|
}
|
|
386
|
-
computeEndpoints() {
|
|
387
|
-
const sr = docRect(this.refs.start);
|
|
388
|
-
const er = docRect(this.refs.end);
|
|
450
|
+
computeEndpoints(sr, er) {
|
|
389
451
|
const ss = this.opts.startSocket ?? "auto";
|
|
390
452
|
const es = this.opts.endSocket ?? "auto";
|
|
391
|
-
return resolveEndpoints(
|
|
453
|
+
return resolveEndpoints(
|
|
454
|
+
sr,
|
|
455
|
+
er,
|
|
456
|
+
ss,
|
|
457
|
+
es,
|
|
458
|
+
this.opts.startSocketOffset ?? 0,
|
|
459
|
+
this.opts.endSocketOffset ?? 0
|
|
460
|
+
);
|
|
392
461
|
}
|
|
393
462
|
render() {
|
|
394
463
|
while (this.group.firstChild) this.group.removeChild(this.group.firstChild);
|
|
395
464
|
this.segments = [];
|
|
396
|
-
|
|
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);
|
|
397
474
|
const origin = overlayOrigin(this.svg);
|
|
398
475
|
const shift = (e) => ({
|
|
399
476
|
start: { x: e.start.x - origin.x, y: e.start.y - origin.y },
|
|
@@ -410,24 +487,29 @@ var ScrollArrow = class {
|
|
|
410
487
|
this.seed,
|
|
411
488
|
this.opts.anchorEnds ?? true
|
|
412
489
|
);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
+
}
|
|
431
513
|
this.appendDrawable(this.rc.path(d, roughOpts), "line");
|
|
432
514
|
const head = this.opts.head;
|
|
433
515
|
const size = this.opts.headSize;
|
|
@@ -538,19 +620,37 @@ var ScrollArrow = class {
|
|
|
538
620
|
if (this.labelBgEl) this.labelBgEl.style.opacity = String(op);
|
|
539
621
|
}
|
|
540
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
|
+
}
|
|
541
641
|
bind() {
|
|
542
642
|
const targets = [this.refs.start, this.refs.end, ...this.resolveAvoid()];
|
|
543
643
|
if (this.refs.target) targets.push(this.refs.target);
|
|
544
644
|
this.ro = new ResizeObserver(() => this.render());
|
|
545
645
|
targets.forEach((t) => this.ro.observe(t));
|
|
546
|
-
if (this.opts.scroll !== false) {
|
|
646
|
+
if (this.opts.scroll !== false && !this.reducedMotion) {
|
|
547
647
|
window.addEventListener("scroll", this.onScroll, true);
|
|
548
648
|
window.addEventListener("resize", this.onScroll);
|
|
549
649
|
}
|
|
550
650
|
}
|
|
551
651
|
update() {
|
|
552
|
-
if (this.destroyed) return;
|
|
553
|
-
if (this.opts.scroll === false) {
|
|
652
|
+
if (this.destroyed || !this.enabled) return;
|
|
653
|
+
if (this.opts.scroll === false || this.reducedMotion) {
|
|
554
654
|
this.applyProgress();
|
|
555
655
|
return;
|
|
556
656
|
}
|
|
@@ -572,6 +672,140 @@ function clampAt(t) {
|
|
|
572
672
|
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
573
673
|
}
|
|
574
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
|
+
|
|
575
809
|
// src/react.tsx
|
|
576
810
|
function read(a) {
|
|
577
811
|
if (typeof a === "string") return a;
|
|
@@ -597,8 +831,33 @@ function ScrollArrowLine(props) {
|
|
|
597
831
|
useScrollArrow(props);
|
|
598
832
|
return null;
|
|
599
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
|
+
}
|
|
600
857
|
|
|
858
|
+
exports.ScrollArrowGroupLines = ScrollArrowGroupLines;
|
|
601
859
|
exports.ScrollArrowLine = ScrollArrowLine;
|
|
602
860
|
exports.useScrollArrow = useScrollArrow;
|
|
861
|
+
exports.useScrollArrowGroup = useScrollArrowGroup;
|
|
603
862
|
//# sourceMappingURL=react.cjs.map
|
|
604
863
|
//# sourceMappingURL=react.cjs.map
|
package/dist/react.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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
5
|
interface UseScrollArrowOptions extends Omit<ScrollArrowOptions, 'start' | 'end'> {
|
|
@@ -16,5 +16,23 @@ declare function useScrollArrow(options: UseScrollArrowOptions): void;
|
|
|
16
16
|
type ScrollArrowProps = UseScrollArrowOptions;
|
|
17
17
|
/** Declarative component form. Renders nothing itself. */
|
|
18
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;
|
|
19
37
|
|
|
20
|
-
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,5 +1,5 @@
|
|
|
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
5
|
interface UseScrollArrowOptions extends Omit<ScrollArrowOptions, 'start' | 'end'> {
|
|
@@ -16,5 +16,23 @@ declare function useScrollArrow(options: UseScrollArrowOptions): void;
|
|
|
16
16
|
type ScrollArrowProps = UseScrollArrowOptions;
|
|
17
17
|
/** Declarative component form. Renders nothing itself. */
|
|
18
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;
|
|
19
37
|
|
|
20
|
-
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
|
|
@@ -5,6 +5,12 @@ type Point = {
|
|
|
5
5
|
/** Which edge of an element the arrow attaches to. `auto` picks the best side. */
|
|
6
6
|
type Socket = 'auto' | 'top' | 'bottom' | 'left' | 'right' | 'center';
|
|
7
7
|
type ArrowHead = 'start' | 'end' | 'both' | 'none';
|
|
8
|
+
/**
|
|
9
|
+
* Line routing style. `curved` (default) is the smooth bezier with single-bend
|
|
10
|
+
* obstacle avoidance. `elbow` draws right-angle connectors (tree/org-chart
|
|
11
|
+
* brackets); `elbow` ignores `avoid`/`curvature`.
|
|
12
|
+
*/
|
|
13
|
+
type Route = 'curved' | 'elbow';
|
|
8
14
|
/** Anything we can resolve to a live DOM element. */
|
|
9
15
|
type ElementRef = Element | string;
|
|
10
16
|
interface ScrollOptions {
|
|
@@ -48,8 +54,24 @@ interface ScrollArrowOptions {
|
|
|
48
54
|
startSocket?: Socket;
|
|
49
55
|
/** Force an end edge. Default "auto". */
|
|
50
56
|
endSocket?: Socket;
|
|
57
|
+
/**
|
|
58
|
+
* Slide the start attach point along its edge, as a fraction of the edge
|
|
59
|
+
* length: `0` (default) = centered, `-0.5`/`+0.5` = the corners. Use to fan
|
|
60
|
+
* out several arrows that leave the same element so they don't stack on one
|
|
61
|
+
* point (e.g. `-0.25`, `0`, `+0.25` for three siblings).
|
|
62
|
+
*/
|
|
63
|
+
startSocketOffset?: number;
|
|
64
|
+
/** Slide the end attach point along its edge. See `startSocketOffset`. */
|
|
65
|
+
endSocketOffset?: number;
|
|
51
66
|
/** Extra bow of the underlying curve, 0..1. Folded into roughness if unset. */
|
|
52
67
|
curvature?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Routing style. `"curved"` (default) is the smooth bezier with obstacle
|
|
70
|
+
* avoidance; `"elbow"` draws right-angle connectors for tree/org-chart
|
|
71
|
+
* diagrams. Elbow mode ignores `avoid` and `curvature`. Pair with explicit
|
|
72
|
+
* `startSocket`/`endSocket` for predictable bracket shapes.
|
|
73
|
+
*/
|
|
74
|
+
route?: Route;
|
|
53
75
|
/**
|
|
54
76
|
* Pin the stroke's endpoints to the anchor sockets so the arrow always lands
|
|
55
77
|
* on its targets, even at high roughness. Set false to let the scratchy ends
|
|
@@ -99,6 +121,62 @@ interface ScrollArrowOptions {
|
|
|
99
121
|
easing?: (t: number) => number;
|
|
100
122
|
/** Start fully drawn instead of empty. Useful with `scroll:false`. Default 0. */
|
|
101
123
|
progress?: number;
|
|
124
|
+
/**
|
|
125
|
+
* Auto-respect `prefers-reduced-motion: reduce`. When the user prefers reduced
|
|
126
|
+
* motion, the arrow renders fully drawn and static (no scroll animation),
|
|
127
|
+
* while still tracking layout. Set false to keep the scroll animation
|
|
128
|
+
* regardless of the OS setting. Default true. Evaluated once at construction.
|
|
129
|
+
*/
|
|
130
|
+
respectReducedMotion?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Initial enabled state. When `false`, the arrow is created hidden and draws
|
|
133
|
+
* nothing until `setEnabled(true)` is called. Use with a `matchMedia` listener
|
|
134
|
+
* to switch arrows off below a breakpoint (where the diagram reflows) without
|
|
135
|
+
* destroying and rebuilding them. Default true.
|
|
136
|
+
*/
|
|
137
|
+
enabled?: boolean;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A coordinated set of arrows that draw in a staggered sequence, driven by one
|
|
141
|
+
* shared scroll trigger (or manually). Each arrow gets a slice of the group's
|
|
142
|
+
* progress so they reveal as `A then B then C` rather than independently.
|
|
143
|
+
*/
|
|
144
|
+
interface ScrollArrowGroupOptions {
|
|
145
|
+
/**
|
|
146
|
+
* The arrows in the group, in reveal order. Each entry takes the usual
|
|
147
|
+
* per-arrow options (roughness, stroke, label, ...); the group forces
|
|
148
|
+
* `scroll: false` on each and drives their progress itself.
|
|
149
|
+
*/
|
|
150
|
+
arrows: ScrollArrowOptions[];
|
|
151
|
+
/**
|
|
152
|
+
* How sequential the reveal is, 0..1. `1` (default) draws each arrow in its
|
|
153
|
+
* own non-overlapping slice (`A` finishes before `B` starts). `0` draws them
|
|
154
|
+
* all at once. Values between overlap the slices.
|
|
155
|
+
*/
|
|
156
|
+
stagger?: number;
|
|
157
|
+
/**
|
|
158
|
+
* Shared scroll behavior for the whole group. Pass `false` to drive the group
|
|
159
|
+
* manually via `setProgress()`. The `target` defaults to a synthetic rect
|
|
160
|
+
* spanning every arrow's endpoints, so the group reveals as it scrolls in.
|
|
161
|
+
*/
|
|
162
|
+
scroll?: ScrollOptions | false;
|
|
163
|
+
/** Multiplier on the group's draw rate. See `ScrollArrowOptions.speed`. Default 1. */
|
|
164
|
+
speed?: number;
|
|
165
|
+
/** Easing applied to the group's overall progress before slicing. Default linear. */
|
|
166
|
+
easing?: (t: number) => number;
|
|
167
|
+
/**
|
|
168
|
+
* Auto-respect `prefers-reduced-motion: reduce` for the whole group. When the
|
|
169
|
+
* user prefers reduced motion, every arrow renders fully drawn and static (no
|
|
170
|
+
* scroll animation). Set false to keep the staggered scroll reveal regardless
|
|
171
|
+
* of the OS setting. Default true. Evaluated once at construction.
|
|
172
|
+
*/
|
|
173
|
+
respectReducedMotion?: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Initial enabled state for the whole group. When `false`, every arrow starts
|
|
176
|
+
* hidden until `setEnabled(true)`. Pairs with a `matchMedia` listener for
|
|
177
|
+
* breakpoint-aware diagrams. Default true.
|
|
178
|
+
*/
|
|
179
|
+
enabled?: boolean;
|
|
102
180
|
}
|
|
103
181
|
|
|
104
|
-
export type { ArrowHead as A, ElementRef as E, Point as P, ScrollArrowOptions as S,
|
|
182
|
+
export type { ArrowHead as A, ElementRef as E, Point as P, ScrollArrowOptions as S, ScrollArrowGroupOptions as a, ScrollOptions as b, Socket as c };
|