|
1 | 1 | <script lang="ts"> |
2 | 2 | import { onMount } from "svelte"; |
3 | 3 | import { scale, fly } from "svelte/transition"; |
4 | | - import type { HTMLAttributes } from "svelte/elements"; |
| 4 | + import type { ClassValue, HTMLAttributes } from "svelte/elements"; |
5 | 5 | import { nonNullish } from "@dfinity/utils"; |
6 | 6 | import { XIcon } from "@lucide/svelte"; |
7 | 7 | import { t } from "$lib/stores/locale.store"; |
|
11 | 11 | closeOnOutsideClick?: boolean; |
12 | 12 | showCloseButton?: boolean; |
13 | 13 | backdrop?: boolean; |
| 14 | + contentClass?: ClassValue | null; |
14 | 15 | }; |
15 | 16 |
|
16 | 17 | const { |
|
20 | 21 | closeOnOutsideClick = nonNullish(onClose), |
21 | 22 | showCloseButton = nonNullish(onClose), |
22 | 23 | backdrop = true, |
| 24 | + contentClass, |
23 | 25 | ...props |
24 | 26 | }: Props = $props(); |
25 | 27 |
|
|
67 | 69 | const preventScroll = (event: TouchEvent) => { |
68 | 70 | event.preventDefault(); |
69 | 71 | }; |
| 72 | + const findScrollableParent = (target: HTMLElement): HTMLElement => { |
| 73 | + let scrollableParent = target; |
| 74 | + while (scrollableParent && scrollableParent !== contentRef) { |
| 75 | + if (scrollableParent === contentRef) { |
| 76 | + break; |
| 77 | + } |
| 78 | + if (scrollableParent.scrollHeight > scrollableParent.clientHeight) { |
| 79 | + return scrollableParent; |
| 80 | + } |
| 81 | + scrollableParent = scrollableParent.parentElement as HTMLElement; |
| 82 | + } |
| 83 | + return contentRef; |
| 84 | + }; |
70 | 85 | let lastY = 0; |
71 | 86 | const touchScrollStart = (event: TouchEvent) => { |
72 | 87 | lastY = event.touches[0].clientY; |
73 | 88 | }; |
74 | 89 | const touchScrollMove = (event: TouchEvent) => { |
| 90 | + const scrollTarget = findScrollableParent(event.target as HTMLElement); |
75 | 91 | const y = event.touches[0].clientY; |
76 | 92 | const dy = y - lastY; |
77 | 93 | lastY = y; |
78 | | - contentRef.scrollTo({ |
79 | | - top: contentRef.scrollTop - dy, |
| 94 | + scrollTarget.scrollTo({ |
| 95 | + top: scrollTarget.scrollTop - dy, |
80 | 96 | behavior: "instant", |
81 | 97 | }); |
82 | 98 | }; |
|
151 | 167 | > |
152 | 168 | <!-- Non-interactive element to render dark-mode bottom sheet border gradient --> |
153 | 169 | <div |
154 | | - class="from-border-secondary pointer-events-none absolute top-0 right-0 left-0 z-0 hidden rounded-t-2xl bg-gradient-to-b to-transparent p-[1px] max-sm:dark:block" |
155 | | - > |
156 | | - <div class="bg-bg-primary_alt h-24 rounded-t-2xl"></div> |
157 | | - </div> |
| 170 | + class={[ |
| 171 | + "from-border-secondary pointer-events-none absolute top-0 right-0 left-0 z-1 hidden h-24 rounded-t-2xl bg-gradient-to-b to-transparent p-[1px] max-sm:dark:block", |
| 172 | + // Use a mask to only show the gradient on the border area, and prevent it from overlapping with the dialog content. |
| 173 | + "![mask-composite:exclude] [mask:linear-gradient(#fff_0_0)_content-box,linear-gradient(#fff_0_0)]", |
| 174 | + ]} |
| 175 | + ></div> |
158 | 176 | <div class="flex flex-1 flex-col"> |
159 | 177 | <div |
160 | 178 | bind:this={contentRef} |
161 | 179 | class="relative max-h-[var(--max-content-height)] overflow-y-auto" |
162 | 180 | > |
163 | 181 | <div |
164 | | - class="flex flex-1 shrink-0 flex-col px-4 pt-4 pb-4 sm:px-6 sm:pt-6 sm:pb-8" |
| 182 | + class={[ |
| 183 | + "flex flex-1 shrink-0 flex-col px-4 pt-4 pb-4 sm:px-6 sm:pt-6 sm:pb-8", |
| 184 | + contentClass, |
| 185 | + ]} |
165 | 186 | > |
166 | 187 | {@render children?.()} |
167 | 188 | {#if showCloseButton && nonNullish(onClose)} |
|
0 commit comments