mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
chore(web): migration svelte 5 syntax (#13883)
This commit is contained in:
parent
9203a61709
commit
0b3742cf13
310 changed files with 6435 additions and 4176 deletions
|
|
@ -14,41 +14,52 @@
|
|||
import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store';
|
||||
import { clickOutside } from '$lib/actions/click-outside';
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
export let icon: string;
|
||||
export let title: string;
|
||||
/**
|
||||
* The alignment of the context menu relative to the button.
|
||||
*/
|
||||
export let align: Align = 'top-left';
|
||||
/**
|
||||
* The direction in which the context menu should open.
|
||||
*/
|
||||
export let direction: 'left' | 'right' = 'right';
|
||||
export let color: Color = 'transparent';
|
||||
export let size: string | undefined = undefined;
|
||||
export let padding: Padding | undefined = undefined;
|
||||
/**
|
||||
* Additional classes to apply to the button.
|
||||
*/
|
||||
export let buttonClass: string | undefined = undefined;
|
||||
export let hideContent = false;
|
||||
interface Props {
|
||||
icon: string;
|
||||
title: string;
|
||||
/**
|
||||
* The alignment of the context menu relative to the button.
|
||||
*/
|
||||
align?: Align;
|
||||
/**
|
||||
* The direction in which the context menu should open.
|
||||
*/
|
||||
direction?: 'left' | 'right';
|
||||
color?: Color;
|
||||
size?: string | undefined;
|
||||
padding?: Padding | undefined;
|
||||
/**
|
||||
* Additional classes to apply to the button.
|
||||
*/
|
||||
buttonClass?: string | undefined;
|
||||
hideContent?: boolean;
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let isOpen = false;
|
||||
let contextMenuPosition = { x: 0, y: 0 };
|
||||
let menuContainer: HTMLUListElement;
|
||||
let buttonContainer: HTMLDivElement;
|
||||
let {
|
||||
icon,
|
||||
title,
|
||||
align = 'top-left',
|
||||
direction = 'right',
|
||||
color = 'transparent',
|
||||
size = undefined,
|
||||
padding = undefined,
|
||||
buttonClass = undefined,
|
||||
hideContent = false,
|
||||
children,
|
||||
}: Props = $props();
|
||||
|
||||
let isOpen = $state(false);
|
||||
let contextMenuPosition = $state({ x: 0, y: 0 });
|
||||
let menuContainer: HTMLUListElement | undefined = $state();
|
||||
let buttonContainer: HTMLDivElement | undefined = $state();
|
||||
|
||||
const id = generateId();
|
||||
const buttonId = `context-menu-button-${id}`;
|
||||
const menuId = `context-menu-${id}`;
|
||||
|
||||
$: {
|
||||
if (isOpen) {
|
||||
$optionClickCallbackStore = handleOptionClick;
|
||||
}
|
||||
}
|
||||
|
||||
const openDropdown = (event: KeyboardEvent | MouseEvent) => {
|
||||
contextMenuPosition = getContextMenuPositionFromEvent(event, align);
|
||||
isOpen = true;
|
||||
|
|
@ -72,9 +83,10 @@
|
|||
};
|
||||
|
||||
const onResize = () => {
|
||||
if (!isOpen) {
|
||||
if (!isOpen || !buttonContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
contextMenuPosition = getContextMenuPositionFromBoundingRect(buttonContainer.getBoundingClientRect(), align);
|
||||
};
|
||||
|
||||
|
|
@ -92,12 +104,19 @@
|
|||
};
|
||||
|
||||
const focusButton = () => {
|
||||
const button: HTMLButtonElement | null = buttonContainer.querySelector(`#${buttonId}`);
|
||||
const button = buttonContainer?.querySelector(`#${buttonId}`) as HTMLButtonElement | null;
|
||||
button?.focus();
|
||||
};
|
||||
|
||||
$effect(() => {
|
||||
if (isOpen) {
|
||||
$optionClickCallbackStore = handleOptionClick;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={onResize} />
|
||||
<svelte:window onresize={onResize} />
|
||||
|
||||
<div
|
||||
use:contextMenuNavigation={{
|
||||
closeDropdown,
|
||||
|
|
@ -109,7 +128,7 @@
|
|||
selectionChanged: (id) => ($selectedIdStore = id),
|
||||
}}
|
||||
use:clickOutside={{ onOutclick: closeDropdown }}
|
||||
on:resize={onResize}
|
||||
onresize={onResize}
|
||||
>
|
||||
<div bind:this={buttonContainer}>
|
||||
<CircleIconButton
|
||||
|
|
@ -123,7 +142,7 @@
|
|||
aria-haspopup={true}
|
||||
class={buttonClass}
|
||||
id={buttonId}
|
||||
on:click={handleClick}
|
||||
onclick={handleClick}
|
||||
/>
|
||||
</div>
|
||||
{#if isOpen || !hideContent}
|
||||
|
|
@ -150,7 +169,7 @@
|
|||
id={menuId}
|
||||
isVisible={isOpen}
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</ContextMenu>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,44 @@
|
|||
import { quintOut } from 'svelte/easing';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { clickOutside } from '$lib/actions/click-outside';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
export let isVisible: boolean = false;
|
||||
export let direction: 'left' | 'right' = 'right';
|
||||
export let x = 0;
|
||||
export let y = 0;
|
||||
export let id: string | undefined = undefined;
|
||||
export let ariaLabel: string | undefined = undefined;
|
||||
export let ariaLabelledBy: string | undefined = undefined;
|
||||
export let ariaActiveDescendant: string | undefined = undefined;
|
||||
interface Props {
|
||||
isVisible?: boolean;
|
||||
direction?: 'left' | 'right';
|
||||
x?: number;
|
||||
y?: number;
|
||||
id?: string | undefined;
|
||||
ariaLabel?: string | undefined;
|
||||
ariaLabelledBy?: string | undefined;
|
||||
ariaActiveDescendant?: string | undefined;
|
||||
menuElement?: HTMLUListElement | undefined;
|
||||
onClose?: (() => void) | undefined;
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
export let menuElement: HTMLUListElement | undefined = undefined;
|
||||
export let onClose: (() => void) | undefined = undefined;
|
||||
let {
|
||||
isVisible = false,
|
||||
direction = 'right',
|
||||
x = 0,
|
||||
y = 0,
|
||||
id = undefined,
|
||||
ariaLabel = undefined,
|
||||
ariaLabelledBy = undefined,
|
||||
ariaActiveDescendant = undefined,
|
||||
menuElement = $bindable(),
|
||||
onClose = undefined,
|
||||
children,
|
||||
}: Props = $props();
|
||||
|
||||
let left: number;
|
||||
let top: number;
|
||||
let left: number = $state(0);
|
||||
let top: number = $state(0);
|
||||
|
||||
// We need to bind clientHeight since the bounding box may return a height
|
||||
// of zero when starting the 'slide' animation.
|
||||
let height: number;
|
||||
let height: number = $state(0);
|
||||
|
||||
$: {
|
||||
$effect(() => {
|
||||
if (menuElement) {
|
||||
const rect = menuElement.getBoundingClientRect();
|
||||
const directionWidth = direction === 'left' ? rect.width : 0;
|
||||
|
|
@ -31,7 +48,7 @@
|
|||
left = Math.min(window.innerWidth - rect.width, x - directionWidth);
|
||||
top = Math.min(window.innerHeight - menuHeight, y);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
|
@ -54,6 +71,6 @@
|
|||
role="menu"
|
||||
tabindex="-1"
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,16 +3,27 @@
|
|||
import { generateId } from '$lib/utils/generate-id';
|
||||
import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store';
|
||||
|
||||
export let text: string;
|
||||
export let subtitle = '';
|
||||
export let icon = '';
|
||||
export let activeColor = 'bg-slate-300';
|
||||
export let textColor = 'text-immich-fg dark:text-immich-dark-bg';
|
||||
export let onClick: () => void;
|
||||
interface Props {
|
||||
text: string;
|
||||
subtitle?: string;
|
||||
icon?: string;
|
||||
activeColor?: string;
|
||||
textColor?: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
text,
|
||||
subtitle = '',
|
||||
icon = '',
|
||||
activeColor = 'bg-slate-300',
|
||||
textColor = 'text-immich-fg dark:text-immich-dark-bg',
|
||||
onClick,
|
||||
}: Props = $props();
|
||||
|
||||
let id: string = generateId();
|
||||
|
||||
$: isActive = $selectedIdStore === id;
|
||||
let isActive = $derived($selectedIdStore === id);
|
||||
|
||||
const handleClick = () => {
|
||||
$optionClickCallbackStore?.();
|
||||
|
|
@ -20,13 +31,13 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
|
||||
<li
|
||||
{id}
|
||||
on:click={handleClick}
|
||||
on:mouseover={() => ($selectedIdStore = id)}
|
||||
on:mouseleave={() => ($selectedIdStore = undefined)}
|
||||
onclick={handleClick}
|
||||
onmouseover={() => ($selectedIdStore = id)}
|
||||
onmouseleave={() => ($selectedIdStore = undefined)}
|
||||
class="w-full p-4 text-left text-sm font-medium {textColor} focus:outline-none focus:ring-2 focus:ring-inset cursor-pointer border-gray-200 flex gap-2 items-center {isActive
|
||||
? activeColor
|
||||
: 'bg-slate-100'}"
|
||||
|
|
|
|||
|
|
@ -1,33 +1,30 @@
|
|||
<script lang="ts">
|
||||
import { tick } from 'svelte';
|
||||
import { tick, type Snippet } from 'svelte';
|
||||
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import { generateId } from '$lib/utils/generate-id';
|
||||
import { contextMenuNavigation } from '$lib/actions/context-menu-navigation';
|
||||
import { optionClickCallbackStore, selectedIdStore } from '$lib/stores/context-menu.store';
|
||||
|
||||
export let title: string;
|
||||
export let direction: 'left' | 'right' = 'right';
|
||||
export let x = 0;
|
||||
export let y = 0;
|
||||
export let isOpen = false;
|
||||
export let onClose: (() => unknown) | undefined;
|
||||
interface Props {
|
||||
title: string;
|
||||
direction?: 'left' | 'right';
|
||||
x?: number;
|
||||
y?: number;
|
||||
isOpen?: boolean;
|
||||
onClose: (() => unknown) | undefined;
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let uniqueKey = {};
|
||||
let menuContainer: HTMLUListElement;
|
||||
let triggerElement: HTMLElement | undefined = undefined;
|
||||
let { title, direction = 'right', x = 0, y = 0, isOpen = false, onClose, children }: Props = $props();
|
||||
|
||||
let uniqueKey = $state({});
|
||||
let menuContainer: HTMLUListElement | undefined = $state();
|
||||
let triggerElement: HTMLElement | undefined = $state(undefined);
|
||||
|
||||
const id = generateId();
|
||||
const menuId = `context-menu-${id}`;
|
||||
|
||||
$: {
|
||||
if (isOpen && menuContainer) {
|
||||
triggerElement = document.activeElement as HTMLElement;
|
||||
menuContainer.focus();
|
||||
$optionClickCallbackStore = closeContextMenu;
|
||||
}
|
||||
}
|
||||
|
||||
const reopenContextMenu = async (event: MouseEvent) => {
|
||||
const contextMenuEvent = new MouseEvent('contextmenu', {
|
||||
bubbles: true,
|
||||
|
|
@ -39,7 +36,7 @@
|
|||
|
||||
const elements = document.elementsFromPoint(event.x, event.y);
|
||||
|
||||
if (elements.includes(menuContainer)) {
|
||||
if (menuContainer && elements.includes(menuContainer)) {
|
||||
// User right-clicked on the context menu itself, we keep the context
|
||||
// menu as is
|
||||
return;
|
||||
|
|
@ -58,6 +55,18 @@
|
|||
triggerElement?.focus();
|
||||
onClose?.();
|
||||
};
|
||||
$effect(() => {
|
||||
if (isOpen && menuContainer) {
|
||||
triggerElement = document.activeElement as HTMLElement;
|
||||
menuContainer.focus();
|
||||
$optionClickCallbackStore = closeContextMenu;
|
||||
}
|
||||
});
|
||||
|
||||
const oncontextmenu = async (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
await reopenContextMenu(event);
|
||||
};
|
||||
</script>
|
||||
|
||||
{#key uniqueKey}
|
||||
|
|
@ -81,11 +90,7 @@
|
|||
},
|
||||
]}
|
||||
>
|
||||
<section
|
||||
class="fixed left-0 top-0 z-10 flex h-screen w-screen"
|
||||
on:contextmenu|preventDefault={reopenContextMenu}
|
||||
role="presentation"
|
||||
>
|
||||
<section class="fixed left-0 top-0 z-10 flex h-screen w-screen" {oncontextmenu} role="presentation">
|
||||
<ContextMenu
|
||||
{direction}
|
||||
{x}
|
||||
|
|
@ -97,7 +102,7 @@
|
|||
isVisible
|
||||
onClose={closeContextMenu}
|
||||
>
|
||||
<slot />
|
||||
{@render children?.()}
|
||||
</ContextMenu>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue