fix: dont allow back navigation on setup

fix: togglegroup navigation in settings
fix: combobox navigation
fix: schedule skeletonloader on android
fix: disable autofocus on modals
fix: episodelist overflowing
fix: mobile player buttons not working on android
This commit is contained in:
ThaUnknown 2025-07-18 01:27:54 +02:00
parent 39aed8afb8
commit 0ed81a4c07
No known key found for this signature in database
13 changed files with 56 additions and 42 deletions

View file

@ -1,6 +1,6 @@
{
"name": "ui",
"version": "6.4.52",
"version": "6.4.53",
"license": "BUSL-1.1",
"private": true,
"packageManager": "pnpm@9.15.5",

View file

@ -56,7 +56,7 @@
</script>
<Pagination count={episodeCount} {perPage} bind:currentPage let:pages let:hasNext let:hasPrev let:range let:setPage siblingCount={1}>
<div class='overflow-y-auto pt-3 -mx-14 px-14 pointer-events-none -mb-3 pb-3' use:dragScroll>
<div class='overflow-y-auto pt-3 -ml-14 pl-14 -mr-3 pr-3 pointer-events-none -mb-3 pb-3' use:dragScroll>
<div class='grid grid-cols-1 sm:grid-cols-[repeat(auto-fit,minmax(500px,1fr))] place-items-center gap-x-4 gap-y-7 justify-center align-middle pointer-events-auto'>
{#each getPage(currentPage, episodeList) as { episode, image, title, summary, airingAt, airdate, filler, length } (episode)}
{@const watched = _progress >= episode && !completed}

View file

@ -162,7 +162,9 @@
</script>
<Dialog.Root bind:open onOpenChange={close} portal='#episodeListTarget'>
<Dialog.Content class='bg-black h-full lg:border-x-4 border-b-0 max-w-5xl w-full max-h-[calc(100%-1rem)] mt-2 p-0 items-center flex lg:rounded-t-xl overflow-hidden z-[100]'>
<Dialog.Content class='bg-black h-full lg:border-x-4 border-b-0 max-w-5xl w-full max-h-[calc(100%-1rem)] mt-2 p-0 items-center flex-col flex lg:rounded-t-xl overflow-hidden z-[100]'>
<!-- this hacky thing is required for dialog root focus trap... pitiful -->
<div class='size-0' tabindex='0' />
{#if $searchStore}
<div class='absolute top-0 left-0 w-full h-full max-h-28 overflow-hidden'>
<Banner media={$searchStore.media} class='object-cover w-full h-full absolute bottom-[0.5px] left-0 -z-10' />
@ -173,7 +175,6 @@
<div class='font-weight-bold text-2xl font-bold text-ellipsis text-nowrap overflow-hidden pb-2'>{title($searchStore.media) ?? ''}</div>
<div class='flex items-center relative scale-parent'>
<Input
autofocus={false}
class='pl-9 bg-background select:bg-accent select:text-accent-foreground shadow-sm no-scale placeholder:opacity-50'
placeholder='Filter by text, or paste a magnet link or torrent file to specify a torrent manually'
bind:value={inputText} />
@ -197,7 +198,7 @@
Auto Select Torrent
</ProgressButton>
</div>
<div class='h-full overflow-y-auto px-4 sm:px-6 pt-2' role='menu' tabindex='-1' on:keydown={stopAnimation} on:pointerenter={stopAnimation} on:pointermove={stopAnimation} use:dragScroll>
<div class='h-full overflow-y-auto px-4 sm:px-6 pt-2' role='menu' tabindex='-1' on:keydown={stopAnimation} on:focusin={stopAnimation} on:pointerenter={stopAnimation} on:pointermove={stopAnimation} use:dragScroll>
{#await Promise.all([searchResult, $downloaded])}
{#each Array.from({ length: 12 }) as _, i (i)}
<div class='p-3 h-[104px] flex cursor-pointer mb-2 relative rounded-md overflow-hidden border border-border flex-col justify-between'>

View file

@ -21,7 +21,7 @@
import { Button } from '$lib/components/ui/button'
import * as Command from '$lib/components/ui/command'
import * as Popover from '$lib/components/ui/popover'
import { inputType } from '$lib/modules/navigate'
import { inputType, navigate } from '$lib/modules/navigate'
import { cn } from '$lib/utils.js'
export let items: readonly value[] = []
@ -84,8 +84,10 @@
</Button>
</Popover.Trigger>
<Popover.Content class={cn('p-0 border-0 z-[1000]')} sameWidth={true}>
<Command.Root>
<Command.Input {placeholder} class='h-9 placeholder:opacity-50' />
<Command.Root onKeydown={navigate}>
<!-- this hacky thing is required for dialog root focus trap... pitiful -->
<div class='h-0 w-full' tabindex='0' />
<Command.Input {placeholder} autofocus={false} class='h-9 placeholder:opacity-50' />
<Command.Empty>No results found.</Command.Empty>
{#if $inputType === 'dpad'}
<Command.Group class='shrink-0' alwaysRender={true}>

View file

@ -1,25 +1,30 @@
<script lang='ts'>
import { Command as CommandPrimitive } from 'cmdk-sv'
import { keywrap } from '$lib/modules/navigate'
import { cn } from '$lib/utils.js'
type $$Props = CommandPrimitive.ItemProps
export let asChild = false
let className: $$Props['class'] = ''
export { className as class }
function click (e: KeyboardEvent) {
const target = e.target as HTMLElement
target.click()
}
</script>
<CommandPrimitive.Item
{asChild}
class={cn(
'aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...$$restProps}
asChild={true}
let:action
let:attrs
>
<slot {action} {attrs} />
<div {...$$restProps} on:keydown={keywrap(click)} use:action {...attrs} tabindex={0} class={cn(
'select:bg-accent select:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}>
<slot {action} {attrs} />
</div>
</CommandPrimitive.Item>

View file

@ -829,7 +829,7 @@
{/if}
{#if !SUPPORTS.isAndroidTV}
<div class='mobile:flex hidden gap-10 absolute items-center transition-opacity select:opacity-100' class:opacity-0={immersed || seeking}>
<Button class='p-3 size-10 pointer-events-auto rounded-[50%] bg-black/20' variant='ghost' disabled={!prev}>
<Button class='p-3 size-10 pointer-events-auto rounded-[50%] bg-black/20' variant='ghost' disabled={!prev} on:click={() => prev?.()}>
<SkipBack fill='currentColor' strokeWidth='1' />
</Button>
<Button class='p-2.5 size-12 pointer-events-auto rounded-[50%] bg-black/20' variant='ghost' on:click={playPause}>
@ -839,7 +839,7 @@
<Pause fill='currentColor' strokeWidth='1' />
{/if}
</Button>
<Button class='p-3 size-10 pointer-events-auto rounded-[50%] bg-black/20' variant='ghost' disabled={!next}>
<Button class='p-3 size-10 pointer-events-auto rounded-[50%] bg-black/20' variant='ghost' disabled={!next} on:click={() => next?.()}>
<SkipForward fill='currentColor' strokeWidth='1' />
</Button>
</div>
@ -871,7 +871,7 @@
<a class='text-white text-lg font-normal leading-none line-clamp-1 hover:text-neutral-300 hover:underline' href='/app/anime/{mediaInfo.media.id}' data-up='#player-options-button-top'>{mediaInfo.session.title}</a>
<Sheet.Root portal={wrapper} bind:open={episodeListOpen}>
<Sheet.Trigger id='episode-list-button' data-down='#player-seekbar' class='text-[rgba(217,217,217,0.6)] hover:text-neutral-500 text-sm leading-none font-light line-clamp-1 text-left hover:underline'>{mediaInfo.session.description}</Sheet.Trigger>
<Sheet.Content class='w-full sm:w-[550px] p-3 sm:p-6 max-w-full sm:max-w-full h-full overflow-y-scroll flex flex-col pb-0 shrink-0 gap-0 bg-black justify-between'>
<Sheet.Content class='w-full sm:w-[550px] p-3 sm:p-6 max-w-full sm:max-w-full h-full overflow-y-scroll flex flex-col pb-0 shrink-0 gap-0 bg-black justify-between overflow-x-clip'>
{#if mediaInfo.media}
{#await Promise.all([episodes(mediaInfo.media.id), client.single(mediaInfo.media.id)]) then [eps, media]}
{#if media.data?.Media}

View file

@ -27,6 +27,7 @@
className
)}
{value}
tabindex={0}
{...$$restProps}
>
<slot />

View file

@ -276,14 +276,16 @@ function focusElement (element?: HTMLElement | null) {
// hacky, but make sure keybinds system loads first so it can prevent this from running
document.addEventListener('keydown', e => {
document.addEventListener('keydown', navigate)
export function navigate (e: KeyboardEvent) {
if (e.key in DirectionKeyMap) {
e.preventDefault()
e.stopPropagation()
inputType.value = 'dpad'
navigateDPad(DirectionKeyMap[e.key as 'ArrowDown' | 'ArrowUp' | 'ArrowLeft' | 'ArrowRight'])
}
})
}
export function dragScroll (node: HTMLElement) {
let x = 0

View file

@ -74,7 +74,7 @@
</script>
<div class='min-w-0 -ml-14 pl-14 grow items-center flex flex-col h-full overflow-y-auto z-10 pointer-events-none pb-10' use:dragScroll on:scroll={handleScroll} bind:this={container}>
<div class='gap-6 w-full pt-4 md:pt-32 flex flex-col items-center justify-center max-w-[1600px] px-3 xl:px-14 pointer-events-auto overflow-x-clip'>
<div class='gap-6 w-full pt-4 md:pt-32 flex flex-col items-center justify-center max-w-[1600px] px-3 xl:px-14 pointer-events-auto'>
<div class='flex flex-col md:flex-row w-full items-center md:items-end gap-5 pt-12'>
<Dialog.Root portal='#root'>
<Dialog.Trigger class='shrink-0 w-[180px] h-[256px] rounded overflow-hidden relative group focus-visible:ring-1 focus-visible:ring-ring select:scale-[1.02] transition-transform duration-200'>
@ -128,7 +128,7 @@
</div>
</div>
</div>
<div class='flex gap-2 items-center md:justify-start md:self-start'>
<div class='flex gap-2 items-center justify-center md:justify-start md:self-start w-full overflow-x-clip [&>*]:flex-shrink-0'>
<div class='flex md:mr-3 w-full min-[380px]:w-[180px]'>
<PlayButton size='default' {media} class='rounded-r-none w-full' />
<EntryEditor {media} />

View file

@ -113,7 +113,7 @@
{#each dayList as { date, number } (date)}
{@const sameMonth = isSameMonth(now, date)}
<div>
<div class='flex flex-col text-xs py-3 h-48' class:opacity-30={!sameMonth}>
<div class='flex flex-col text-xs py-3 h-24 lg:h-48' class:opacity-30={!sameMonth}>
<div class={cn('w-6 h-6 flex items-center justify-center font-bold mx-3', isToday(date) && 'bg-[rgb(61,180,242)] rounded-full')}>
{number}
</div>

View file

@ -6,6 +6,7 @@
import { Switch } from '$lib/components/ui/switch'
import * as ToggleGroup from '$lib/components/ui/toggle-group'
import native from '$lib/modules/native'
import { navigate } from '$lib/modules/navigate'
import { settings, languageCodes, subtitleResolutions, SUPPORTS } from '$lib/modules/settings'
async function selectPlayer () {
@ -29,22 +30,24 @@
</SettingCard>
<SettingCard class='md:flex-col md:items-start' title='Subtitle Dialogue Style Overrides' description={'Selectively override the default dialogue style for subtitles. This will not change the style of typesetting [Fancy 3D Signs and Songs].\n\nWarning: the heuristic used for deciding when to override the style is rather rough, and enabling this option can lead to incorrectly rendered subtitles.'}>
<ToggleGroup.Root type='single' class='grid sm:grid-cols-2 gap-3' bind:value={$settings.subtitleStyle}>
<ToggleGroup.Item value='none' class='h-auto aspect-video text-4xl px-0 relative'>
<div class='absolute top-4 text-xl font-bold'>None</div>🚫
</ToggleGroup.Item>
<ToggleGroup.Item value='gandhisans' class='h-auto px-0 relative'>
<div class='absolute top-4 text-xl font-bold'>Gandhi Sans Bold</div>
<img src='/gandhisans.png' class='w-full' />
</ToggleGroup.Item>
<ToggleGroup.Item value='notosans' class='h-auto px-0 relative'>
<div class='absolute top-4 text-xl font-bold'>Noto Sans Bold</div>
<img src='/notosans.png' class='w-full' />
</ToggleGroup.Item>
<ToggleGroup.Item value='roboto' class='h-auto px-0 relative'>
<div class='absolute top-4 text-xl font-bold'>Roboto Bold</div>
<img src='/roboto.png' class='w-full' />
</ToggleGroup.Item>
<ToggleGroup.Root type='single' bind:value={$settings.subtitleStyle} asChild let:builder>
<div class='grid sm:grid-cols-2 gap-3' use:builder.action {...builder} on:keydown|capture|stopPropagation={navigate}>
<ToggleGroup.Item value='none' class='h-auto aspect-video text-4xl px-0 relative'>
<div class='absolute top-4 text-xl font-bold'>None</div>🚫
</ToggleGroup.Item>
<ToggleGroup.Item value='gandhisans' class='h-auto px-0 relative'>
<div class='absolute top-4 text-xl font-bold'>Gandhi Sans Bold</div>
<img src='/gandhisans.png' class='w-full' />
</ToggleGroup.Item>
<ToggleGroup.Item value='notosans' class='h-auto px-0 relative'>
<div class='absolute top-4 text-xl font-bold'>Noto Sans Bold</div>
<img src='/notosans.png' class='w-full' />
</ToggleGroup.Item>
<ToggleGroup.Item value='roboto' class='h-auto px-0 relative'>
<div class='absolute top-4 text-xl font-bold'>Roboto Bold</div>
<img src='/roboto.png' class='w-full' />
</ToggleGroup.Item>
</div>
</ToggleGroup.Root>
</SettingCard>

View file

@ -23,5 +23,5 @@
I agree to the <a use:click={() => native.openURL(`${WEB_URL}/terms`)} class='text-primary underline'>Terms of Service</a> and <a use:click={() => native.openURL(`${WEB_URL}/privacy`)} class='text-primary underline'>Privacy Policy</a>
</Label>
</div>
<Button class='text-lg font-bold shrink-0' disabled={!checked} size='lg' href={checked ? './storage' : undefined}>Start Setup</Button>
<Button class='text-lg font-bold shrink-0' disabled={!checked} size='lg' href={checked ? './storage' : undefined} data-sveltekit-replacestate>Start Setup</Button>
</div>

View file

@ -29,7 +29,7 @@
function checkNext () {
if (step === 2) localStorage.setItem('setup-finished', SETUP_VERSION.toString())
goto(`../${NEXT[step]}`)
goto(`../${NEXT[step]}`, { replaceState: true })
}
</script>