• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OneBusAway / wayfinder / 29559896664

17 Jul 2026 06:23AM UTC coverage: 84.483%. First build
29559896664

Pull #560

github

web-flow
Merge cb0fa7101 into 5894b956a
Pull Request #560: Stop bottom sheet at all viewport widths + calmer map route labels

2267 of 2502 branches covered (90.61%)

Branch coverage included in aggregate %.

315 of 342 new or added lines in 14 files covered. (92.11%)

13593 of 16271 relevant lines covered (83.54%)

4.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.87
/src/components/navigation/BottomSheet.svelte
1
<!--
1✔
2
    @component
3
    A draggable bottom sheet anchored to the bottom edge of its nearest positioned
4
    ancestor, with three snap points (peek / half / full). Content behind it stays
5
    visible and interactive. Drag the handle row (including the header) to resize;
6
    on the grab handle, arrow keys step between snap points and Home/End jump to
7
    the extremes.
8

9
    @prop {('peek'|'half'|'full')} snap - Bindable current snap point
10
    @prop {import('svelte').Snippet} header - Condensed header rendered inside the drag-handle row
11
    @prop {import('svelte').Snippet} children - Scrollable sheet body
12
-->
13

14
<script>
15
        import '$lib/i18n.js';
16
        import { t } from 'svelte-i18n';
17

18
        let { snap = $bindable('half'), header, children } = $props();
1✔
19

20
        const SNAP_ORDER = ['peek', 'half', 'full'];
1✔
21
        const PEEK_HEIGHT = 150;
1✔
22
        const HALF_FRACTION = 0.55;
1✔
23
        const MIN_DRAG_HEIGHT = 120;
1✔
24

25
        let containerHeight = $state(0);
1✔
26
        // One in-flight gesture: null when idle, { y, startHeight, height } while dragging.
27
        let drag = $state(null);
1✔
28

29
        let dragging = $derived(drag !== null);
1✔
30

31
        let snapHeights = $derived({
1✔
32
                peek: PEEK_HEIGHT,
1✔
33
                half: Math.round(containerHeight * HALF_FRACTION),
1✔
34
                full: containerHeight
1✔
35
        });
36

37
        let sheetHeight = $derived(drag?.height ?? snapHeights[snap]);
1✔
38

39
        function nearestSnap(height) {
1✔
40
                return SNAP_ORDER.reduce((closest, candidate) =>
1✔
41
                        Math.abs(snapHeights[candidate] - height) < Math.abs(snapHeights[closest] - height)
2!
NEW
42
                                ? candidate
×
43
                                : closest
2✔
44
                );
45
        }
46

47
        function handlePointerDown(event) {
1✔
48
                // Let taps on the header's own controls (close, view details, ...) behave
49
                // as clicks; capturing the pointer here would swallow them.
50
                if (event.target.closest('a, button')) return;
1✔
51

52
                event.currentTarget.setPointerCapture?.(event.pointerId);
3!
53
                drag = { y: event.clientY, startHeight: sheetHeight, height: sheetHeight };
1✔
54
        }
55

56
        function handlePointerMove(event) {
1✔
57
                if (!drag) return;
1✔
58
                const height = drag.startHeight + (drag.y - event.clientY);
1✔
59
                drag.height = Math.max(MIN_DRAG_HEIGHT, Math.min(containerHeight, height));
1✔
60
        }
61

62
        function handlePointerUp() {
1✔
63
                if (!drag) return;
1✔
64
                snap = nearestSnap(sheetHeight);
1✔
65
                drag = null;
1✔
66
        }
67

68
        function handleKeydown(event) {
1✔
69
                const index = SNAP_ORDER.indexOf(snap);
1✔
70
                let nextIndex = null;
1✔
71

72
                if (event.key === 'ArrowUp' || event.key === 'ArrowRight') {
1✔
73
                        nextIndex = Math.min(index + 1, SNAP_ORDER.length - 1);
2✔
74
                } else if (event.key === 'ArrowDown' || event.key === 'ArrowLeft') {
1✔
75
                        nextIndex = Math.max(index - 1, 0);
3✔
76
                } else if (event.key === 'Home') {
1✔
77
                        nextIndex = 0;
1✔
78
                } else if (event.key === 'End') {
1✔
79
                        nextIndex = SNAP_ORDER.length - 1;
1✔
80
                }
81

82
                if (nextIndex !== null) {
1✔
83
                        event.preventDefault();
1✔
84
                        snap = SNAP_ORDER[nextIndex];
1✔
85
                }
86
        }
87
</script>
88

89
<div class="pointer-events-none absolute inset-0" bind:clientHeight={containerHeight}>
1✔
90
        <div
91
                class="pointer-events-auto absolute inset-x-0 bottom-0 flex flex-col overflow-hidden rounded-t-[14px] border border-b-0 border-gray-400 bg-surface/95 shadow-[0_-8px_24px_rgba(0,0,0,.18)] backdrop-blur dark:border-gray-600 dark:bg-surface-dark/95 dark:text-surface-foreground-dark"
92
                style:height="{sheetHeight}px"
1!
93
                style:transition={dragging ? 'none' : 'height .28s cubic-bezier(0,0,.2,1)'}
1✔
94
                data-testid="bottom-sheet"
95
        >
96
                <div
97
                        role="presentation"
98
                        class="flex-none cursor-grab touch-none px-3.5 pb-1.5 pt-2"
99
                        onpointerdown={handlePointerDown}
1✔
100
                        onpointermove={handlePointerMove}
1✔
101
                        onpointerup={handlePointerUp}
1✔
102
                        onpointercancel={handlePointerUp}
1✔
103
                >
104
                        <div
105
                                role="slider"
106
                                tabindex="0"
107
                                aria-label={$t('sheet.resize_handle')}
1✔
108
                                aria-orientation="vertical"
109
                                aria-valuemin="0"
110
                                aria-valuemax={SNAP_ORDER.length - 1}
1✔
111
                                aria-valuenow={SNAP_ORDER.indexOf(snap)}
1✔
112
                                aria-valuetext={$t(`sheet.snap_${snap}`)}
1✔
113
                                onkeydown={handleKeydown}
1✔
114
                                class="mx-auto mb-2 block h-1 w-10 rounded-full bg-gray-300 dark:bg-gray-600"
115
                        ></div>
116

117
                        {@render header?.()}
118
                </div>
119

120
                <div
121
                        class="flex-1 overflow-y-auto overscroll-contain px-3"
122
                        style:padding-bottom="calc(1rem + env(safe-area-inset-bottom))"
123
                >
124
                        {@render children?.()}
125
                </div>
126
        </div>
127
</div>
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc