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/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 = { x: start.x + sn.x * reach + belly.x, y: start.y + sn.y * reach + belly.y };
93
- const c2 = { x: end.x + en.x * reach + belly.x, y: end.y + en.y * reach + belly.y };
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") container.style.position = "relative";
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.progress = clamp01(this.opts.progress);
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(sr, er, ss, es);
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
- const ep = this.computeEndpoints();
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
- const obstacles = this.resolveAvoid().map((el) => {
407
- const dr = docRect(el);
408
- return {
409
- left: dr.left - origin.x,
410
- top: dr.top - origin.y,
411
- width: dr.width,
412
- height: dr.height
413
- };
414
- });
415
- const clear = routeOffset(
416
- local.start,
417
- local.end,
418
- obstacles,
419
- this.opts.avoidPadding ?? 14
420
- );
421
- const BOW = 1.6;
422
- const belly = { x: clear.x * BOW, y: clear.y * BOW };
423
- const d = buildPath(local, curvature, belly);
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(Math.max(0, at * total - eps));
470
- const after = this.lineEl.getPointAtLength(Math.min(total, at * total + eps));
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-DehQP2Hx.cjs';
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, "start" | "end"> {
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
- interface ScrollArrowProps extends UseScrollArrowOptions {
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-DehQP2Hx.js';
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, "start" | "end"> {
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
- interface ScrollArrowProps extends UseScrollArrowOptions {
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-HLZXSGP5.js';
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