refactor: settings accordion reactivity (#28281)

This commit is contained in:
Daniel Dietzler
2026-05-07 21:00:23 +02:00
committed by GitHub
parent 52b00b0bad
commit 2039c129f2
8 changed files with 178 additions and 202 deletions
@@ -1,10 +1,8 @@
<script lang="ts">
import { accordionManager } from '$lib/managers/accordion-manager.svelte';
import { Icon } from '@immich/ui';
import { onDestroy, onMount, type Snippet } from 'svelte';
import { onDestroy, type Snippet } from 'svelte';
import { slide } from 'svelte/transition';
import { getAccordionState } from './SettingAccordionState.svelte';
const accordionState = getAccordionState();
interface Props {
title: string;
@@ -21,7 +19,7 @@
title,
subtitle = '',
key,
isOpen = $bindable($accordionState.has(key)),
isOpen = $bindable(false),
autoScrollTo = false,
icon = '',
subtitleSnippet,
@@ -30,9 +28,15 @@
let accordionElement: HTMLDivElement | undefined = $state();
const setIsOpen = (isOpen: boolean) => {
$effect(() => {
isOpen = accordionManager.isOpen(key);
});
const toggleOpen = () => {
if (isOpen) {
$accordionState = $accordionState.add(key);
accordionManager.close(key);
} else {
accordionManager.open(key);
if (autoScrollTo) {
setTimeout(() => {
@@ -42,24 +46,11 @@
});
}, 200);
}
} else {
$accordionState.delete(key);
// eslint-disable-next-line no-self-assign
$accordionState = $accordionState;
}
};
onDestroy(() => {
setIsOpen(false);
});
const onclick = () => {
isOpen = !isOpen;
setIsOpen(isOpen);
};
onMount(() => {
setIsOpen(isOpen);
accordionManager.close(key);
});
</script>
@@ -72,7 +63,7 @@
<button
type="button"
aria-expanded={isOpen}
{onclick}
onclick={toggleOpen}
class="flex w-full place-items-center justify-between text-start"
>
<div>
@@ -1,43 +0,0 @@
<script lang="ts" module>
export type AccordionState = Set<string>;
const { get: getAccordionState, set: setAccordionState } = createContext<Writable<AccordionState>>();
export { getAccordionState };
</script>
<script lang="ts">
import { writable, type Writable } from 'svelte/store';
import { createContext } from '$lib/utils/context';
import { page } from '$app/state';
import { goto } from '$app/navigation';
import type { Snippet } from 'svelte';
import { handlePromiseError } from '$lib/utils';
import { SvelteURLSearchParams } from 'svelte/reactivity';
const getParamValues = (param: string) => {
return new Set((page.url.searchParams.get(param) || '').split(' ').filter((x) => x !== ''));
};
interface Props {
queryParam: string;
state?: Writable<AccordionState>;
children?: Snippet;
}
let { queryParam, state = writable(getParamValues(queryParam)), children }: Props = $props();
setAccordionState(state);
const searchParams = new SvelteURLSearchParams(page.url.searchParams);
$effect(() => {
if ($state.size > 0) {
searchParams.set(queryParam, [...$state].join(' '));
} else {
searchParams.delete(queryParam);
}
handlePromiseError(goto(`?${searchParams.toString()}`, { replaceState: true, noScroll: true, keepFocus: true }));
});
</script>
{@render children?.()}
@@ -0,0 +1,46 @@
import { SvelteSet, SvelteURLSearchParams } from 'svelte/reactivity';
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { QueryParameter } from '$lib/constants';
import { handlePromiseError } from '$lib/utils';
class AccordionManager {
// needs to be derived since `page.url.searchParams` isn't actually initialized by the time this class gets instantiated.
#searchParams = $derived(new SvelteURLSearchParams(page.url.searchParams));
#state = $derived(
new SvelteSet(
this.#searchParams
.get(QueryParameter.IS_OPEN)
?.split(' ')
.filter((x) => x !== ''),
),
);
isOpen(key: string) {
return this.#state.has(key);
}
#refreshSearchParams() {
if (this.#state.size === 0) {
this.#searchParams.delete(QueryParameter.IS_OPEN);
} else {
this.#searchParams.set(QueryParameter.IS_OPEN, [...this.#state].join(' '));
}
handlePromiseError(
goto(`?${this.#searchParams.toString()}`, { replaceState: true, noScroll: true, keepFocus: true }),
);
}
open(key: string) {
this.#state.add(key);
this.#refreshSearchParams();
}
close(key: string) {
this.#state.delete(key);
this.#refreshSearchParams();
}
}
export const accordionManager = new AccordionManager();
-8
View File
@@ -1,8 +0,0 @@
import { getContext, setContext } from 'svelte';
export function createContext<T>(key: string | symbol = Symbol()) {
return {
get: () => getContext<T>(key),
set: (context: T) => setContext<T>(key, context),
};
}