fix: Custom scrollIntoView function with offsets and onFocus handlers for containers
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { type NavigationActions, type RevealStrategy, Selectable } from './lib/selectable';
|
||||
import { type NavigationActions, type FocusHandler, Selectable } from './lib/selectable';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export let name: string = '';
|
||||
@@ -12,8 +12,7 @@
|
||||
export let canFocusEmpty = true;
|
||||
export let trapFocus = false;
|
||||
export let debugOutline = false;
|
||||
export let revealStrategy: RevealStrategy | undefined = undefined; //TODO: change to on:focus
|
||||
export let childrenRevealStrategy: RevealStrategy | undefined = undefined;
|
||||
export let handleFocus: (selectable: Selectable) => void = () => {};
|
||||
|
||||
export let active = true;
|
||||
|
||||
@@ -23,10 +22,9 @@
|
||||
.setDirection(direction === 'grid' ? 'horizontal' : direction)
|
||||
.setGridColumns(gridCols)
|
||||
.setNavigationActions(navigationActions)
|
||||
.setRevealStrategy(revealStrategy)
|
||||
.setChildrenRevealStrategy(childrenRevealStrategy)
|
||||
.setTrapFocus(trapFocus)
|
||||
.setCanFocusEmpty(canFocusEmpty)
|
||||
.setOnFocus(handleFocus)
|
||||
.getStores();
|
||||
export const container = rest.container;
|
||||
export const hasFocus = rest.hasFocus;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import type { TitleType } from '../../types';
|
||||
import Container from '../../../Container.svelte';
|
||||
import { useNavigate } from 'svelte-navigator';
|
||||
import { scrollWithOffset } from '../../selectable';
|
||||
import { scrollIntoView } from '../../selectable';
|
||||
|
||||
export let tmdbId: number | undefined = undefined;
|
||||
export let tvdbId: number | undefined = undefined;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Container from '../../Container.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { scrollWithOffset } from '../selectable';
|
||||
|
||||
let cols: number = 1;
|
||||
const calculateRows = () => {
|
||||
@@ -25,7 +24,6 @@
|
||||
<Container
|
||||
direction="grid"
|
||||
gridCols={cols}
|
||||
childrenRevealStrategy={scrollWithOffset('all', 50)}
|
||||
class="grid gap-x-4 gap-y-8 grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5"
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { ChevronLeft, ChevronRight } from 'radix-icons-svelte';
|
||||
import classNames from 'classnames';
|
||||
import Container from '../../../Container.svelte';
|
||||
import { scrollWithOffset } from '../../selectable';
|
||||
|
||||
export let gradientFromColor = 'from-stone-950';
|
||||
export let heading = '';
|
||||
@@ -45,11 +44,7 @@
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<Container
|
||||
childrenRevealStrategy={scrollWithOffset('left', 64 + 16)}
|
||||
direction="horizontal"
|
||||
navigationActions={{ left: () => true }}
|
||||
>
|
||||
<Container direction="horizontal" navigationActions={{ left: () => true }}>
|
||||
<div
|
||||
class={classNames(
|
||||
'flex overflow-x-scroll items-center overflow-y-visible relative scrollbar-hide',
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
}black 5%, black 95%, ${fadeRight ? '' : 'black 100%, '}transparent 100%);`}
|
||||
on:scroll={updateScrollPosition}
|
||||
bind:this={element}
|
||||
navigationActions={{ left: () => true }}
|
||||
>
|
||||
<slot />
|
||||
</Container>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Container from '../../../Container.svelte';
|
||||
import HeroShowcaseBackground from './HeroBackground.svelte';
|
||||
import { scrollWithOffset, Selectable } from '../../selectable';
|
||||
import { scrollIntoView, Selectable } from '../../selectable';
|
||||
import IconButton from '../IconButton.svelte';
|
||||
import { ChevronRight } from 'radix-icons-svelte';
|
||||
import PageDots from '../HeroShowcase/PageDots.svelte';
|
||||
@@ -42,7 +42,6 @@
|
||||
<Container class="flex-1 flex">
|
||||
<HeroShowcaseBackground {urls} {index} />
|
||||
<Container
|
||||
revealStrategy={scrollWithOffset('up', 0)}
|
||||
navigationActions={{
|
||||
right: onNext,
|
||||
left: onPrevious,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { TmdbEpisode, TmdbSeason } from '../../apis/tmdb/tmdb-api';
|
||||
import type { TmdbEpisode } from '../../apis/tmdb/tmdb-api';
|
||||
import Container from '../../../Container.svelte';
|
||||
import classNames from 'classnames';
|
||||
import { TMDB_BACKDROP_SMALL } from '../../constants';
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { tmdbApi, type TmdbSeason, type TmdbSeriesFull2 } from '../../apis/tmdb/tmdb-api';
|
||||
import Carousel from '../Carousel/Carousel.svelte';
|
||||
import Container from '../../../Container.svelte';
|
||||
import { scrollWithOffset } from '../../selectable';
|
||||
import { scrollElementIntoView, scrollIntoView } from '../../selectable';
|
||||
import UICarousel from '../Carousel/UICarousel.svelte';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -20,8 +20,22 @@
|
||||
(series) => (series?.seasons?.length ? ([series.seasons.length] as const) : undefined)
|
||||
);
|
||||
|
||||
const episodeContainers: Record<string, Container> = {};
|
||||
|
||||
function handleSelectSeason(season: TmdbSeason) {
|
||||
console.log(season);
|
||||
const episode = season.episodes?.[0];
|
||||
if (episode) {
|
||||
console.log(
|
||||
episode,
|
||||
episodeContainers,
|
||||
`episode-${episode.id}`,
|
||||
episodeContainers[`episode-${episode.id}`]
|
||||
);
|
||||
const selectable = episodeContainers[`episode-${episode.id}`]?.container;
|
||||
if (selectable) {
|
||||
selectable.focus(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -35,6 +49,11 @@
|
||||
let:hasFocus
|
||||
class="mx-2 text-nowrap"
|
||||
on:click={() => handleSelectSeason(season)}
|
||||
handleFocus={(s) => {
|
||||
const element = s.getHtmlElement();
|
||||
if (element) scrollElementIntoView(element, { horizontal: 64 });
|
||||
handleSelectSeason(season);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class={classNames({
|
||||
@@ -50,9 +69,13 @@
|
||||
<div class="flex">
|
||||
{#each $tmdbSeasons as season}
|
||||
{#each season?.episodes || [] as episode}
|
||||
<div class="mx-2">
|
||||
<Container
|
||||
class="mx-2"
|
||||
bind:this={episodeContainers[`episode-${episode.id}`]}
|
||||
handleFocus={scrollIntoView({ left: 64 + 16 })}
|
||||
>
|
||||
<EpisodeCard {episode} />
|
||||
</div>
|
||||
</Container>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import ManageMediaModal from '../ManageMedia/ManageMediaModal.svelte';
|
||||
import { derived } from 'svelte/store';
|
||||
import EpisodeCarousel from './EpisodeCarousel.svelte';
|
||||
import { scrollIntoView } from '../../selectable';
|
||||
|
||||
export let id: string;
|
||||
|
||||
@@ -46,7 +47,10 @@
|
||||
</script>
|
||||
|
||||
<DetachedPage>
|
||||
<div class="h-screen flex flex-col py-12 px-20 relative">
|
||||
<Container
|
||||
class="h-screen flex flex-col py-12 px-20 relative"
|
||||
handleFocus={scrollIntoView({ top: 0 })}
|
||||
>
|
||||
<HeroCarousel
|
||||
urls={$tmdbSeries.then(
|
||||
(series) =>
|
||||
@@ -143,6 +147,8 @@
|
||||
{/await}
|
||||
</div>
|
||||
</HeroCarousel>
|
||||
</div>
|
||||
</Container>
|
||||
<Container handleFocus={scrollIntoView({ vertical: 64 })}>
|
||||
<EpisodeCarousel id={Number(id)} tmdbSeries={tmdbSeriesData} />
|
||||
</Container>
|
||||
</DetachedPage>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import CarouselPlaceholderItems from '../components/Carousel/CarouselPlaceholderItems.svelte';
|
||||
import HeroShowcase from '../components/HeroShowcase/HeroShowcase.svelte';
|
||||
import { getShowcasePropsFromTmdb } from '../components/HeroShowcase/HeroShowcase';
|
||||
import { scrollWithOffset } from '../selectable';
|
||||
import { scrollIntoView } from '../selectable';
|
||||
import SidebarMargin from '../components/SidebarMargin.svelte';
|
||||
|
||||
const jellyfinItemsPromise = new Promise<JellyfinItem[]>((resolve) => {
|
||||
@@ -75,8 +75,10 @@
|
||||
</script>
|
||||
|
||||
<Container focusOnMount>
|
||||
<div class="h-screen flex flex-col">
|
||||
<div class="flex flex-col h-screen">
|
||||
<div class="flex-1 flex relative px-20">
|
||||
<HeroShowcase items={tmdbApi.getPopularMovies().then(getShowcasePropsFromTmdb)} />
|
||||
</div>
|
||||
<div class="mt-8">
|
||||
<Carousel scrollClass="">
|
||||
<SidebarMargin slot="title" class="mx-4">
|
||||
@@ -89,30 +91,12 @@
|
||||
{:then props}
|
||||
<div class="w-[4.5rem] h-1 shrink-0" />
|
||||
{#each props as prop (prop.tmdbId)}
|
||||
<div class="m-2">
|
||||
<Container class="m-2" handleFocus={scrollIntoView({ left: 64 + 16 })}>
|
||||
<Card {...prop} />
|
||||
</div>
|
||||
</Container>
|
||||
{/each}
|
||||
{/await}
|
||||
</Carousel>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <Carousel scrollClass="px-2 sm:px-8 2xl:px-16">-->
|
||||
<!-- <div slot="title" class="text-lg font-semibold text-zinc-300">-->
|
||||
<!-- {$_('discover.library')}-->
|
||||
<!-- </div>-->
|
||||
<!-- {#await fetchLibraryItems()}-->
|
||||
<!-- <CarouselPlaceholderItems />-->
|
||||
<!-- {:then props}-->
|
||||
<!-- {#each props as prop (prop.tmdbId)}-->
|
||||
<!-- <Container>-->
|
||||
<!-- <Poster {...prop} />-->
|
||||
<!-- </Container>-->
|
||||
<!-- {/each}-->
|
||||
<!-- {/await}-->
|
||||
<!-- </Carousel>-->
|
||||
<!-- <Poster-->
|
||||
<!-- backdropUrl="http://192.168.0.129:8096/Items/8cc44d55dba1495a2ffcda104286d611/Images/Primary?quality=80&fillWidth=432&tag=d026e7eb1d9ba9934c8769695e396dc4"-->
|
||||
<!-- />-->
|
||||
<!-- <VideoPlayer jellyfinId="8cc44d55dba1495a2ffcda104286d611" />-->
|
||||
</Container>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import TmdbCard from '../components/Card/TmdbCard.svelte';
|
||||
import Button from '../components/Button.svelte';
|
||||
import { useNavigate } from 'svelte-navigator';
|
||||
import { scrollIntoView } from '../selectable';
|
||||
|
||||
const popularMovies = tmdbApi.getPopularMovies();
|
||||
const navigate = useNavigate();
|
||||
@@ -32,9 +33,9 @@
|
||||
{:then items}
|
||||
<div class="w-[4.5rem] h-1 shrink-0" />
|
||||
{#each items as item (item.id)}
|
||||
<div class="m-2">
|
||||
<Container class="m-2" handleFocus={scrollIntoView({ left: 64 + 16 })}>
|
||||
<TmdbCard {item} />
|
||||
</div>
|
||||
</Container>
|
||||
{/each}
|
||||
{/await}
|
||||
</Carousel>
|
||||
|
||||
@@ -11,90 +11,7 @@ export type NavigationActions = {
|
||||
enter?: (selectable: Selectable) => boolean;
|
||||
};
|
||||
|
||||
export type RevealStrategy = (target: Selectable) => void;
|
||||
|
||||
export const scrollWithOffset =
|
||||
(side: Direction | 'all' = 'all', offset = 50): RevealStrategy =>
|
||||
(target) => {
|
||||
function getScrollParent(node: HTMLElement): HTMLElement | undefined {
|
||||
const parent = node.parentElement;
|
||||
|
||||
if (parent) {
|
||||
if (parent.scrollHeight > parent.clientHeight || parent.scrollWidth > parent.clientWidth) {
|
||||
return parent;
|
||||
} else {
|
||||
return getScrollParent(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scrollIntoView(offset = 0, direction: Direction = 'left') {
|
||||
const targetHtmlElement = target.getHtmlElement();
|
||||
if (targetHtmlElement) {
|
||||
const boundingRect = targetHtmlElement.getBoundingClientRect();
|
||||
|
||||
const leftOffset = targetHtmlElement.offsetLeft;
|
||||
const rightOffset = targetHtmlElement.offsetLeft + boundingRect.width;
|
||||
const topOffset = targetHtmlElement.offsetTop;
|
||||
const bottomOffset = targetHtmlElement.offsetTop + boundingRect.height;
|
||||
|
||||
const offsetParent = getScrollParent(targetHtmlElement);
|
||||
|
||||
if (offsetParent) {
|
||||
const parentBoundingRect = offsetParent.getBoundingClientRect();
|
||||
|
||||
const scrollLeft = offsetParent.scrollLeft;
|
||||
const scrollRight =
|
||||
offsetParent.scrollLeft + Math.min(parentBoundingRect.width, window.innerWidth);
|
||||
const scrollTop = offsetParent.scrollTop;
|
||||
const scrollBottom =
|
||||
offsetParent.scrollTop + Math.min(parentBoundingRect.height, window.innerHeight);
|
||||
|
||||
if (side === 'all') {
|
||||
const left =
|
||||
leftOffset - offset < scrollLeft
|
||||
? leftOffset - offset
|
||||
: rightOffset + offset > scrollRight
|
||||
? rightOffset - Math.min(parentBoundingRect.width, window.innerWidth) + offset
|
||||
: -1;
|
||||
const top =
|
||||
topOffset - offset < scrollTop
|
||||
? topOffset - offset
|
||||
: bottomOffset + offset > scrollBottom
|
||||
? bottomOffset - Math.min(parentBoundingRect.height, window.innerHeight) + offset
|
||||
: -1;
|
||||
|
||||
if (left !== -1 || top !== -1) {
|
||||
offsetParent.scrollTo({
|
||||
...(left !== -1 && { left }),
|
||||
...(top !== -1 && { top }),
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
} else if (side === 'left' || side === 'right') {
|
||||
const left = {
|
||||
left: leftOffset - offset,
|
||||
right: rightOffset - parentBoundingRect.width + offset
|
||||
}[side];
|
||||
|
||||
offsetParent.scrollTo({
|
||||
left,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
} else if (side === 'up' || side === 'down') {
|
||||
const top = {
|
||||
up: topOffset - offset,
|
||||
down: bottomOffset - parentBoundingRect.height + offset
|
||||
}[side];
|
||||
|
||||
offsetParent.scrollTo({
|
||||
top,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
export type FocusHandler = (target: Selectable) => void;
|
||||
|
||||
export class Selectable {
|
||||
id: symbol;
|
||||
@@ -114,8 +31,7 @@ export class Selectable {
|
||||
private isInitialized: boolean = false;
|
||||
private navigationActions: NavigationActions = {};
|
||||
private isActive: boolean = true;
|
||||
private scrollIntoView?: RevealStrategy;
|
||||
private scrollChildrenIntoView?: RevealStrategy;
|
||||
private onFocus?: (selectable: Selectable) => void;
|
||||
|
||||
private direction: FlowDirection = 'vertical';
|
||||
private gridColumns: number = 0;
|
||||
@@ -159,7 +75,7 @@ export class Selectable {
|
||||
return this;
|
||||
}
|
||||
|
||||
focus() {
|
||||
focus(navigate: boolean = true) {
|
||||
function updateFocusIndex(currentSelectable: Selectable, selectable?: Selectable) {
|
||||
if (selectable) {
|
||||
const index = currentSelectable.children.indexOf(selectable);
|
||||
@@ -170,22 +86,19 @@ export class Selectable {
|
||||
}
|
||||
}
|
||||
|
||||
if (!get(this.hasFocusWithin)) {
|
||||
if (this.scrollIntoView) this.scrollIntoView(this);
|
||||
else if (this.parent?.getScrollChildrenIntoView())
|
||||
this.parent?.getScrollChildrenIntoView()?.(this);
|
||||
}
|
||||
if (!get(this.hasFocusWithin)) this.onFocus?.(this);
|
||||
|
||||
if (this.children.length > 0) {
|
||||
const focusIndex = get(this.focusIndex);
|
||||
|
||||
if (this.children[focusIndex]?.isFocusable()) {
|
||||
this.children[focusIndex]?.focus();
|
||||
this.children[focusIndex]?.focus(navigate);
|
||||
} else {
|
||||
let i = focusIndex;
|
||||
while (i < this.children.length) {
|
||||
if (this.children[i]?.isFocusable()) {
|
||||
this.children[i]?.focus();
|
||||
this.children[i]?.focus(navigate);
|
||||
// this.onFocus?.(this);
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
@@ -193,18 +106,20 @@ export class Selectable {
|
||||
i = focusIndex - 1;
|
||||
while (i >= 0) {
|
||||
if (this.children[i]?.isFocusable()) {
|
||||
this.children[i]?.focus();
|
||||
this.children[i]?.focus(navigate);
|
||||
// this.onFocus?.(this);
|
||||
return;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
}
|
||||
} else if (this.htmlElement) {
|
||||
this.htmlElement.focus({ preventScroll: true });
|
||||
// this.htmlElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
|
||||
// this.scrollIntoView(50);
|
||||
Selectable.focusedObject.set(this);
|
||||
updateFocusIndex(this);
|
||||
|
||||
if (navigate) {
|
||||
this.htmlElement.focus({ preventScroll: true });
|
||||
Selectable.focusedObject.set(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,6 +391,10 @@ export class Selectable {
|
||||
}
|
||||
}
|
||||
|
||||
getFocusedChild() {
|
||||
return this.children[get(this.focusIndex)];
|
||||
}
|
||||
|
||||
setNavigationActions(actions: NavigationActions) {
|
||||
this.navigationActions = actions;
|
||||
return this;
|
||||
@@ -503,20 +422,6 @@ export class Selectable {
|
||||
return this.htmlElement;
|
||||
}
|
||||
|
||||
setRevealStrategy(revealStrategy?: RevealStrategy) {
|
||||
this.scrollIntoView = revealStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
setChildrenRevealStrategy(revealStrategy?: RevealStrategy) {
|
||||
this.scrollChildrenIntoView = revealStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
getScrollChildrenIntoView() {
|
||||
return this.scrollChildrenIntoView;
|
||||
}
|
||||
|
||||
setTrapFocus(trapFocus: boolean) {
|
||||
this.trapFocus = trapFocus;
|
||||
return this;
|
||||
@@ -526,6 +431,11 @@ export class Selectable {
|
||||
this.canFocusEmpty = canFocusEmpty;
|
||||
return this;
|
||||
}
|
||||
|
||||
setOnFocus(onFocus: typeof this.onFocus) {
|
||||
this.onFocus = onFocus;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export function handleKeyboardNavigation(event: KeyboardEvent) {
|
||||
@@ -559,3 +469,201 @@ export function handleKeyboardNavigation(event: KeyboardEvent) {
|
||||
}
|
||||
|
||||
// Selectable.focusedObject.subscribe(console.log);
|
||||
|
||||
type Offsets = Partial<
|
||||
Record<
|
||||
'top' | 'bottom' | 'left' | 'right' | 'horizontal' | 'vertical' | 'all',
|
||||
number | undefined
|
||||
>
|
||||
>;
|
||||
export const scrollElementIntoView = (htmlElement: HTMLElement, offsets: Offsets = { all: 16 }) => {
|
||||
function getScrollParent(
|
||||
node: HTMLElement,
|
||||
direction: 'vertical' | 'horizontal'
|
||||
): HTMLElement | undefined {
|
||||
const parent = node.parentElement;
|
||||
|
||||
if (parent) {
|
||||
if (
|
||||
(direction === 'vertical' && parent.scrollHeight > parent.clientHeight) ||
|
||||
(direction === 'horizontal' && parent.scrollWidth > parent.clientWidth)
|
||||
) {
|
||||
return parent;
|
||||
} else {
|
||||
return getScrollParent(parent, direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (offsets.vertical !== undefined) {
|
||||
offsets.top = offsets.vertical;
|
||||
offsets.bottom = offsets.vertical;
|
||||
}
|
||||
|
||||
if (offsets.horizontal !== undefined) {
|
||||
offsets.left = offsets.horizontal;
|
||||
offsets.right = offsets.horizontal;
|
||||
}
|
||||
|
||||
if (offsets.all !== undefined) {
|
||||
offsets.top = offsets.all;
|
||||
offsets.bottom = offsets.all;
|
||||
offsets.left = offsets.all;
|
||||
offsets.right = offsets.all;
|
||||
}
|
||||
|
||||
const boundingRect = htmlElement.getBoundingClientRect();
|
||||
const verticalParent = getScrollParent(htmlElement, 'vertical');
|
||||
const horizontalParent = getScrollParent(htmlElement, 'horizontal');
|
||||
|
||||
if (verticalParent && (offsets.top !== undefined || offsets.bottom !== undefined)) {
|
||||
const parentBoundingRect = verticalParent.getBoundingClientRect();
|
||||
|
||||
let top = -1;
|
||||
|
||||
if (offsets.top !== undefined && offsets.bottom !== undefined) {
|
||||
top =
|
||||
boundingRect.y - parentBoundingRect.y < offsets.top
|
||||
? boundingRect.y - parentBoundingRect.y + verticalParent.scrollTop - offsets.top
|
||||
: boundingRect.y - parentBoundingRect.y + htmlElement.clientHeight >
|
||||
verticalParent.clientHeight - offsets.bottom
|
||||
? boundingRect.y -
|
||||
parentBoundingRect.y +
|
||||
htmlElement.clientHeight +
|
||||
verticalParent.scrollTop +
|
||||
offsets.bottom -
|
||||
verticalParent.clientHeight
|
||||
: -1;
|
||||
} else if (offsets.top !== undefined) {
|
||||
top = boundingRect.y - parentBoundingRect.y + verticalParent.scrollTop - offsets.top;
|
||||
} else if (offsets.bottom !== undefined) {
|
||||
top =
|
||||
boundingRect.y -
|
||||
parentBoundingRect.y +
|
||||
htmlElement.clientHeight +
|
||||
verticalParent.scrollTop +
|
||||
offsets.bottom -
|
||||
verticalParent.clientHeight;
|
||||
}
|
||||
|
||||
if (top !== -1) {
|
||||
verticalParent.scrollTo({
|
||||
behavior: 'smooth',
|
||||
top
|
||||
});
|
||||
}
|
||||
}
|
||||
if (horizontalParent && (offsets.left !== undefined || offsets.right !== undefined)) {
|
||||
const parentBoundingRect = horizontalParent.getBoundingClientRect();
|
||||
|
||||
let left = -1;
|
||||
|
||||
if (offsets.left !== undefined && offsets.right !== undefined) {
|
||||
left =
|
||||
boundingRect.x - parentBoundingRect.x < offsets.left
|
||||
? boundingRect.x - parentBoundingRect.x + horizontalParent.scrollLeft - offsets.left
|
||||
: boundingRect.x - parentBoundingRect.x + htmlElement.clientWidth >
|
||||
horizontalParent.clientWidth - offsets.right
|
||||
? boundingRect.x -
|
||||
parentBoundingRect.x +
|
||||
htmlElement.clientWidth +
|
||||
horizontalParent.scrollLeft +
|
||||
offsets.right -
|
||||
horizontalParent.clientWidth
|
||||
: -1;
|
||||
} else if (offsets.left !== undefined) {
|
||||
left = boundingRect.x - parentBoundingRect.x + horizontalParent.scrollLeft - offsets.left;
|
||||
} else if (offsets.right !== undefined) {
|
||||
left =
|
||||
boundingRect.x -
|
||||
parentBoundingRect.x +
|
||||
htmlElement.clientWidth +
|
||||
horizontalParent.scrollLeft +
|
||||
offsets.right -
|
||||
horizontalParent.clientWidth;
|
||||
}
|
||||
|
||||
if (left !== -1) {
|
||||
horizontalParent.scrollTo({
|
||||
behavior: 'smooth',
|
||||
left
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// export const _scrollElementIntoView = (
|
||||
// htmlElement: HTMLElement,
|
||||
// direction: 'vertical' | 'horizontal',
|
||||
// offset: number = 16
|
||||
// ) => {
|
||||
// function getScrollParent(node: HTMLElement): HTMLElement | undefined {
|
||||
// const parent = node.parentElement;
|
||||
//
|
||||
// if (parent) {
|
||||
// if (
|
||||
// (direction === 'vertical' && parent.scrollHeight > parent.clientHeight) ||
|
||||
// (direction === 'horizontal' && parent.scrollWidth > parent.clientWidth)
|
||||
// ) {
|
||||
// return parent;
|
||||
// } else {
|
||||
// return getScrollParent(parent);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// const boundingRect = htmlElement.getBoundingClientRect();
|
||||
// const parent = getScrollParent(htmlElement);
|
||||
//
|
||||
// if (parent) {
|
||||
// const parentBoundingRect = parent.getBoundingClientRect();
|
||||
//
|
||||
// const left =
|
||||
// boundingRect.x - parentBoundingRect.x < offset
|
||||
// ? boundingRect.x - parentBoundingRect.x + parent.scrollLeft - offset
|
||||
// : boundingRect.x - parentBoundingRect.x + htmlElement.clientWidth >
|
||||
// parent.clientWidth - offset
|
||||
// ? boundingRect.x -
|
||||
// parentBoundingRect.x +
|
||||
// htmlElement.clientWidth +
|
||||
// parent.scrollLeft +
|
||||
// offset -
|
||||
// parent.clientWidth
|
||||
// : -1;
|
||||
//
|
||||
// const top =
|
||||
// boundingRect.y - parentBoundingRect.y < offset
|
||||
// ? boundingRect.y - parentBoundingRect.y + parent.scrollTop - offset
|
||||
// : boundingRect.y - parentBoundingRect.y + htmlElement.clientHeight >
|
||||
// parent.clientHeight - offset
|
||||
// ? boundingRect.y -
|
||||
// parentBoundingRect.y +
|
||||
// htmlElement.clientHeight +
|
||||
// parent.scrollTop +
|
||||
// offset -
|
||||
// parent.clientHeight
|
||||
// : -1;
|
||||
//
|
||||
// parent.scrollTo({
|
||||
// behavior: 'smooth',
|
||||
// ...(top !== -1 && direction === 'vertical' && { top }),
|
||||
// ...(left !== -1 && direction === 'vertical' && { left })
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// export const scrollElementIntoView = (
|
||||
// htmlElement: HTMLElement,
|
||||
// offsets: Partial<
|
||||
// Record<'up' | 'down' | 'left' | 'right' | 'horizontal' | 'vertical' | 'all', number | undefined>
|
||||
// > = { all: 16 }
|
||||
// ) => {};
|
||||
|
||||
export const scrollIntoView: (...args: [Offsets]) => FocusHandler =
|
||||
(...args) =>
|
||||
(s) => {
|
||||
const element = s.getHtmlElement();
|
||||
if (element) {
|
||||
scrollElementIntoView(element, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user