Skip to content

Commit 3219def

Browse files
authored
fix: handle border widths and shadow dom (#237)
* add shadow-dom test * fix(shadowdom): add test and attempt support * working on new test suite * test new test suite on ci * finished borders test case * handle border widths * optimize * cleanup lil bit
1 parent 524e6ab commit 3219def

File tree

4 files changed

+282
-42
lines changed

4 files changed

+282
-42
lines changed

src/compute.ts

+44-41
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,26 @@ declare global {
1414
width: number
1515
}
1616
}
17+
18+
// @TODO better declaration of possible shadowdom hosts
19+
interface Element {
20+
host: any
21+
}
1722
}
1823

1924
import { CustomScrollAction, Options } from './types'
2025

21-
const isElement = el => el != null && typeof el == 'object' && el.nodeType === 1
22-
const hasScrollableSpace = (el, axis: 'Y' | 'X') => {
23-
if (axis === 'Y') {
24-
return el.clientHeight < el.scrollHeight
25-
}
26+
// @TODO better shadowdom test, 11 = document fragment
27+
const isElement = el =>
28+
el != null &&
29+
typeof el == 'object' &&
30+
(el.nodeType === 1 || el.nodeType === 11)
2631

27-
if (axis === 'X') {
28-
return el.clientWidth < el.scrollWidth
29-
}
32+
const hasScrollableSpace = (el, axis: 'X' | 'Y') =>
33+
axis === 'X'
34+
? el.clientWidth < el.scrollWidth
35+
: el.clientHeight < el.scrollHeight
3036

31-
return false
32-
}
3337
const canOverflow = (
3438
el,
3539
axis: 'Y' | 'X',
@@ -63,6 +67,8 @@ const alignNearest = (
6367
scrollingEdgeStart: number,
6468
scrollingEdgeEnd: number,
6569
scrollingSize: number,
70+
scrollingBorderStart: number,
71+
scrollingBorderEnd: number,
6672
elementEdgeStart: number,
6773
elementEdgeEnd: number,
6874
elementSize: number
@@ -139,7 +145,7 @@ const alignNearest = (
139145
(elementEdgeStart < scrollingEdgeStart && elementSize < scrollingSize) ||
140146
(elementEdgeEnd > scrollingEdgeEnd && elementSize > scrollingSize)
141147
) {
142-
return elementEdgeStart - scrollingEdgeStart
148+
return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart
143149
}
144150

145151
/**
@@ -186,7 +192,7 @@ const alignNearest = (
186192
(elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||
187193
(elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)
188194
) {
189-
return elementEdgeEnd - scrollingEdgeEnd
195+
return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd
190196
}
191197

192198
return 0
@@ -219,7 +225,11 @@ export default (
219225
// Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box
220226
const frames: Element[] = []
221227
let parent
222-
while (isElement((parent = target.parentNode)) && checkBoundary(target)) {
228+
// @TODO have a better shadowdom test here
229+
while (
230+
isElement((parent = target.parentNode || target.host)) &&
231+
checkBoundary(target)
232+
) {
223233
if (
224234
isScrollable(parent, skipOverflowHiddenElements) ||
225235
parent === viewport
@@ -277,12 +287,15 @@ export default (
277287
// Collect new scroll positions
278288
const computations = frames.map((frame): CustomScrollAction => {
279289
const frameRect = frame.getBoundingClientRect()
280-
// @TODO fix hardcoding of block => top/Y
290+
const frameStyle = getComputedStyle(frame)
291+
const borderLeft = parseInt(frameStyle.borderLeftWidth as string, 10)
292+
const borderTop = parseInt(frameStyle.borderTopWidth as string, 10)
293+
const borderRight = parseInt(frameStyle.borderRightWidth as string, 10)
294+
const borderBottom = parseInt(frameStyle.borderBottomWidth as string, 10)
281295

282296
let blockScroll = 0
283297
let inlineScroll = 0
284298

285-
// @TODO handle borders
286299
// @TODO fix the if else pyramid nightmare
287300

288301
if (block === 'start') {
@@ -297,9 +310,7 @@ export default (
297310
targetBlock - frameRect.top,
298311
frame.scrollHeight - frame.clientHeight - frame.scrollTop
299312
)
300-
blockScroll = frame.scrollTop + offset
301-
302-
targetBlock -= blockScroll - frame.scrollTop
313+
blockScroll = frame.scrollTop + offset - borderTop
303314
}
304315
}
305316
if (block === 'center') {
@@ -318,9 +329,6 @@ export default (
318329
)
319330

320331
blockScroll = frame.scrollTop + offset
321-
322-
// Cache the offset so that parent frames can scroll this into view correctly
323-
targetBlock += frame.scrollTop - blockScroll
324332
}
325333
}
326334

@@ -335,10 +343,7 @@ export default (
335343
const offset =
336344
0 - Math.min(frameRect.bottom - targetBlock, frame.scrollTop)
337345

338-
blockScroll = frame.scrollTop + offset
339-
340-
// Cache the offset so that parent frames can scroll this into view correctly
341-
targetBlock += frame.scrollTop - blockScroll
346+
blockScroll = frame.scrollTop + offset + borderBottom
342347
}
343348
}
344349

@@ -352,6 +357,8 @@ export default (
352357
viewportY,
353358
viewportY + viewportHeight,
354359
viewportHeight,
360+
borderTop,
361+
borderBottom,
355362
viewportY + targetBlock,
356363
viewportY + targetBlock + targetRect.height,
357364
targetRect.height
@@ -363,14 +370,13 @@ export default (
363370
frameRect.top,
364371
frameRect.bottom,
365372
frameRect.height,
373+
borderTop,
374+
borderBottom,
366375
targetBlock,
367376
targetBlock + targetRect.height,
368377
targetRect.height
369378
)
370379
blockScroll = frame.scrollTop + offset
371-
372-
// Cache the offset so that parent frames can scroll this into view correctly
373-
targetBlock -= offset
374380
}
375381
}
376382

@@ -386,9 +392,7 @@ export default (
386392
targetInline - frameRect.left,
387393
frame.scrollHeight - frame.clientLeft - frame.scrollLeft
388394
)
389-
inlineScroll = frame.scrollLeft + offset
390-
391-
targetInline -= inlineScroll - frame.scrollLeft
395+
inlineScroll = frame.scrollLeft + offset - borderLeft
392396
}
393397
}
394398

@@ -408,9 +412,6 @@ export default (
408412
)
409413

410414
inlineScroll = frame.scrollLeft + offset
411-
412-
// Cache the offset so that parent frames can scroll this into view correctly
413-
targetInline += frame.scrollLeft - inlineScroll
414415
}
415416
}
416417

@@ -425,10 +426,7 @@ export default (
425426
const offset =
426427
0 - Math.min(frameRect.right - targetInline, frame.scrollLeft)
427428

428-
inlineScroll = frame.scrollLeft + offset
429-
430-
// Cache the offset so that parent frames can scroll this into view correctly
431-
targetInline += frame.scrollLeft - inlineScroll
429+
inlineScroll = frame.scrollLeft + offset + borderRight
432430
}
433431
}
434432

@@ -442,6 +440,8 @@ export default (
442440
viewportX,
443441
viewportX + viewportWidth,
444442
viewportWidth,
443+
borderLeft,
444+
borderRight,
445445
viewportX + targetInline,
446446
viewportX + targetInline + targetRect.width,
447447
targetRect.width
@@ -453,18 +453,21 @@ export default (
453453
frameRect.left,
454454
frameRect.right,
455455
frameRect.width,
456+
borderLeft,
457+
borderRight,
456458
targetInline,
457459
targetInline + targetRect.width,
458460
targetRect.width
459461
)
460462

461463
inlineScroll = frame.scrollLeft + offset
462-
463-
// Cache the offset so that parent frames can scroll this into view correctly
464-
targetInline -= offset
465464
}
466465
}
467466

467+
// Cache the offset so that parent frames can scroll this into view correctly
468+
targetBlock += frame.scrollTop - blockScroll
469+
targetInline += frame.scrollLeft - inlineScroll
470+
468471
return { el: frame, top: blockScroll, left: inlineScroll }
469472
})
470473

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE HTML>
2+
<script src="/node_modules/scroll-into-view-if-needed/umd/scroll-into-view-if-needed.js"></script>
3+
<script src="/resources/testharness.js"></script>
4+
<script src="/resources/testharnessreport.js"></script>
5+
<title>Check End Position of scrollIntoView of shadow elements</title>
6+
<div id="container">
7+
<div id="space1" style="height: 2000px; width: 2000px;background-color: yellow">
8+
</div>
9+
<div id="shadow"></div>
10+
<div id="space2" style="height: 2000px; width: 2000px;background-color: blue">
11+
</div>
12+
</div>
13+
<script>
14+
add_completion_callback(() => document.getElementById("container").remove());
15+
16+
test(t => {
17+
var shadow = document.getElementById("shadow");
18+
var shadowRoot = shadow.attachShadow({ mode: "open" });
19+
var shadowDiv = document.createElement("div");
20+
shadowDiv.style.height = "200px";
21+
shadowDiv.style.width = "200px";
22+
shadowDiv.style.backgroundColor = "green";
23+
shadowRoot.appendChild(shadowDiv);
24+
25+
window.scrollTo(0, 0);
26+
var expected_x = shadowDiv.offsetLeft;
27+
var expected_y = shadowDiv.offsetTop;
28+
assert_not_equals(window.scrollX, expected_x);
29+
assert_not_equals(window.scrollY, expected_y);
30+
scrollIntoView(shadowDiv, {block: "start", inline: "start"});
31+
assert_approx_equals(window.scrollX, expected_x, 1);
32+
assert_approx_equals(window.scrollY, expected_y, 1);
33+
}, "scrollIntoView should behave correctly if applies to shadow dom elements");
34+
</script>

0 commit comments

Comments
 (0)