concertina 0.1.2 → 0.3.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/index.cjs CHANGED
@@ -34,10 +34,24 @@ function pinToScrollTop(el) {
34
34
  let parent = el.parentElement;
35
35
  while (parent) {
36
36
  const { overflowY } = getComputedStyle(parent);
37
- if (overflowY === "auto" || overflowY === "scroll") {
37
+ if ((overflowY === "auto" || overflowY === "scroll") && parent.scrollHeight > parent.clientHeight) {
38
38
  const box = parent.getBoundingClientRect();
39
39
  const target = el.getBoundingClientRect();
40
- parent.scrollTop += target.top - box.top;
40
+ let stickyOffset = 0;
41
+ const measure = (node) => {
42
+ const s = getComputedStyle(node);
43
+ if (s.position === "sticky") {
44
+ stickyOffset = Math.max(
45
+ stickyOffset,
46
+ (parseFloat(s.top) || 0) + node.getBoundingClientRect().height
47
+ );
48
+ }
49
+ };
50
+ for (const child of parent.children) {
51
+ measure(child);
52
+ for (const gc of child.children) measure(gc);
53
+ }
54
+ parent.scrollTop += target.top - box.top - stickyOffset;
41
55
  return;
42
56
  }
43
57
  parent = parent.parentElement;
package/dist/index.d.cts CHANGED
@@ -28,11 +28,15 @@ interface UseConcertinaReturn {
28
28
  declare function useConcertina(): UseConcertinaReturn;
29
29
 
30
30
  /**
31
- * Scroll `el` to the top of its nearest scrollable ancestor.
31
+ * Scroll `el` to the top of its nearest scrollable ancestor,
32
+ * clearing any sticky headers. Only adjusts one container's
33
+ * scrollTop. Never cascades to the viewport, which matters on
34
+ * mobile where scrollIntoView pulls the whole page.
32
35
  *
33
- * Only adjusts one container's scrollTop. Never cascades to the
34
- * viewport, which matters on mobile where scrollIntoView pulls
35
- * the whole page.
36
+ * Skips elements that have overflow: auto/scroll in CSS but
37
+ * don't actually scroll (scrollHeight <= clientHeight). Without
38
+ * this check, a non-scrolling ancestor with overflow-auto traps
39
+ * the walk and the real scroll container never gets adjusted.
36
40
  */
37
41
  declare function pinToScrollTop(el: HTMLElement | null): void;
38
42
 
package/dist/index.d.ts CHANGED
@@ -28,11 +28,15 @@ interface UseConcertinaReturn {
28
28
  declare function useConcertina(): UseConcertinaReturn;
29
29
 
30
30
  /**
31
- * Scroll `el` to the top of its nearest scrollable ancestor.
31
+ * Scroll `el` to the top of its nearest scrollable ancestor,
32
+ * clearing any sticky headers. Only adjusts one container's
33
+ * scrollTop. Never cascades to the viewport, which matters on
34
+ * mobile where scrollIntoView pulls the whole page.
32
35
  *
33
- * Only adjusts one container's scrollTop. Never cascades to the
34
- * viewport, which matters on mobile where scrollIntoView pulls
35
- * the whole page.
36
+ * Skips elements that have overflow: auto/scroll in CSS but
37
+ * don't actually scroll (scrollHeight <= clientHeight). Without
38
+ * this check, a non-scrolling ancestor with overflow-auto traps
39
+ * the walk and the real scroll container never gets adjusted.
36
40
  */
37
41
  declare function pinToScrollTop(el: HTMLElement | null): void;
38
42
 
package/dist/index.js CHANGED
@@ -13,10 +13,24 @@ function pinToScrollTop(el) {
13
13
  let parent = el.parentElement;
14
14
  while (parent) {
15
15
  const { overflowY } = getComputedStyle(parent);
16
- if (overflowY === "auto" || overflowY === "scroll") {
16
+ if ((overflowY === "auto" || overflowY === "scroll") && parent.scrollHeight > parent.clientHeight) {
17
17
  const box = parent.getBoundingClientRect();
18
18
  const target = el.getBoundingClientRect();
19
- parent.scrollTop += target.top - box.top;
19
+ let stickyOffset = 0;
20
+ const measure = (node) => {
21
+ const s = getComputedStyle(node);
22
+ if (s.position === "sticky") {
23
+ stickyOffset = Math.max(
24
+ stickyOffset,
25
+ (parseFloat(s.top) || 0) + node.getBoundingClientRect().height
26
+ );
27
+ }
28
+ };
29
+ for (const child of parent.children) {
30
+ measure(child);
31
+ for (const gc of child.children) measure(gc);
32
+ }
33
+ parent.scrollTop += target.top - box.top - stickyOffset;
20
34
  return;
21
35
  }
22
36
  parent = parent.parentElement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "concertina",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "React hook for scroll-pinned Radix Accordion panels. Zero dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",