chore(web): migration svelte 5 syntax (#13883)

This commit is contained in:
Alex 2024-11-14 08:43:25 -06:00 committed by GitHub
parent 9203a61709
commit 0b3742cf13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
310 changed files with 6435 additions and 4176 deletions

View file

@ -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}

View file

@ -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>

View file

@ -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'}"

View file

@ -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>