refactor: migrate createEventDispatcher to Svelte 5 callback props

Migrate 5 components from Svelte 4 createEventDispatcher to Svelte 5 callback props:
- DropdownMenu.svelte (removed unused dispatcher)
- ProjectListItem.svelte (edit, togglePublish, delete events)
- PostListItem.svelte (edit, togglePublish, delete events)
- AlbumListItem.svelte (toggleDropdown, edit, togglePublish, delete events)
- InlineComposerModal.svelte (close, saved events + migrate export let to $props)

Updated parent components to use onevent={handler} syntax instead of on:event={handler}.
This commit is contained in:
Justin Edmund 2025-11-04 19:35:53 -08:00
parent d964bf05cd
commit 4337b57dee
8 changed files with 58 additions and 57 deletions

View file

@ -1,6 +1,5 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { createEventDispatcher } from 'svelte'
import AdminByline from './AdminByline.svelte'
interface Photo {
@ -33,16 +32,13 @@
interface Props {
album: Album
isDropdownActive?: boolean
ontoggledropdown?: (event: CustomEvent<{ albumId: number; event: MouseEvent }>) => void
onedit?: (event: CustomEvent<{ album: Album; event: MouseEvent }>) => void
ontogglepublish?: (event: CustomEvent<{ album: Album; event: MouseEvent }>) => void
ondelete?: (event: CustomEvent<{ album: Album; event: MouseEvent }>) => void
}
let { album, isDropdownActive = false }: Props = $props()
const dispatch = createEventDispatcher<{
toggleDropdown: { albumId: number; event: MouseEvent }
edit: { album: Album; event: MouseEvent }
togglePublish: { album: Album; event: MouseEvent }
delete: { album: Album; event: MouseEvent }
}>()
let { album, isDropdownActive = false, ontoggledropdown, onedit, ontogglepublish, ondelete }: Props = $props()
function formatRelativeTime(dateString: string): string {
const date = new Date(dateString)
@ -72,19 +68,19 @@
}
function handleToggleDropdown(event: MouseEvent) {
dispatch('toggleDropdown', { albumId: album.id, event })
ontoggledropdown?.(new CustomEvent('toggledropdown', { detail: { albumId: album.id, event } }))
}
function handleEdit(event: MouseEvent) {
dispatch('edit', { album, event })
onedit?.(new CustomEvent('edit', { detail: { album, event } }))
}
function handleTogglePublish(event: MouseEvent) {
dispatch('togglePublish', { album, event })
ontogglepublish?.(new CustomEvent('togglepublish', { detail: { album, event } }))
}
function handleDelete(event: MouseEvent) {
dispatch('delete', { album, event })
ondelete?.(new CustomEvent('delete', { detail: { album, event } }))
}
// Get thumbnail - try cover photo first, then first photo

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte'
import { onMount } from 'svelte'
import { browser } from '$app/environment'
import { computePosition, flip, shift, offset, autoUpdate } from '@floating-ui/dom'
import ChevronRight from '$icons/chevron-right.svg?component'
@ -26,7 +26,6 @@
let dropdownElement: HTMLDivElement
let cleanup: (() => void) | null = null
const dispatch = createEventDispatcher()
// Track which submenu is open
let openSubmenuId = $state<string | null>(null)

View file

@ -1,5 +1,4 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { goto } from '$app/navigation'
import Modal from './Modal.svelte'
import Composer from './composer'
@ -13,11 +12,25 @@
import type { JSONContent } from '@tiptap/core'
import type { Media } from '@prisma/client'
export let isOpen = false
export let initialMode: 'modal' | 'page' = 'modal'
export let initialPostType: 'post' | 'essay' = 'post'
export let initialContent: JSONContent | undefined = undefined
export let closeOnSave = true
interface Props {
isOpen?: boolean
initialMode?: 'modal' | 'page'
initialPostType?: 'post' | 'essay'
initialContent?: JSONContent
closeOnSave?: boolean
onclose?: (event: CustomEvent) => void
onsaved?: (event: CustomEvent) => void
}
let {
isOpen = $bindable(false),
initialMode = 'modal',
initialPostType = 'post',
initialContent = undefined,
closeOnSave = true,
onclose,
onsaved
}: Props = $props()
type PostType = 'post' | 'essay'
type ComposerMode = 'modal' | 'page'
@ -48,7 +61,6 @@
let isMediaDetailsOpen = false
const CHARACTER_LIMIT = 600
const dispatch = createEventDispatcher()
function handleClose() {
if (hasContent() && !confirm('Are you sure you want to close? Your changes will be lost.')) {
@ -56,7 +68,7 @@
}
resetComposer()
isOpen = false
dispatch('close')
onclose?.(new CustomEvent('close'))
}
function hasContent(): boolean {
@ -207,7 +219,7 @@
if (closeOnSave) {
isOpen = false
}
dispatch('saved')
onsaved?.(new CustomEvent('saved'))
if (postType === 'essay') {
goto('/admin/posts')
}

View file

@ -1,20 +1,17 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { createEventDispatcher, onMount } from 'svelte'
import { onMount } from 'svelte'
import AdminByline from './AdminByline.svelte'
import type { AdminPost } from '$lib/types/admin'
interface Props {
post: AdminPost
onedit?: (event: CustomEvent<{ post: AdminPost }>) => void
ontogglepublish?: (event: CustomEvent<{ post: AdminPost }>) => void
ondelete?: (event: CustomEvent<{ post: AdminPost }>) => void
}
let { post }: Props = $props()
const dispatch = createEventDispatcher<{
edit: { post: AdminPost }
togglePublish: { post: AdminPost }
delete: { post: AdminPost }
}>()
let { post, onedit, ontogglepublish, ondelete }: Props = $props()
let isDropdownOpen = $state(false)
@ -38,19 +35,19 @@
function handleEdit(event: MouseEvent) {
event.stopPropagation()
dispatch('edit', { post })
onedit?.(new CustomEvent('edit', { detail: { post } }))
goto(`/admin/posts/${post.id}/edit`)
}
function handleTogglePublish(event: MouseEvent) {
event.stopPropagation()
dispatch('togglePublish', { post })
ontogglepublish?.(new CustomEvent('togglepublish', { detail: { post } }))
isDropdownOpen = false
}
function handleDelete(event: MouseEvent) {
event.stopPropagation()
dispatch('delete', { post })
ondelete?.(new CustomEvent('delete', { detail: { post } }))
isDropdownOpen = false
}

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { createEventDispatcher, onMount } from 'svelte'
import { onMount } from 'svelte'
import AdminByline from './AdminByline.svelte'
import { clickOutside } from '$lib/actions/clickOutside'
@ -8,15 +8,12 @@
interface Props {
project: AdminProject
onedit?: (event: CustomEvent<{ project: AdminProject }>) => void
ontogglepublish?: (event: CustomEvent<{ project: AdminProject }>) => void
ondelete?: (event: CustomEvent<{ project: AdminProject }>) => void
}
let { project }: Props = $props()
const dispatch = createEventDispatcher<{
edit: { project: AdminProject }
togglePublish: { project: AdminProject }
delete: { project: AdminProject }
}>()
let { project, onedit, ontogglepublish, ondelete }: Props = $props()
let isDropdownOpen = $state(false)
@ -57,15 +54,15 @@
}
function handleEdit() {
dispatch('edit', { project })
onedit?.(new CustomEvent('edit', { detail: { project } }))
}
function handleTogglePublish() {
dispatch('togglePublish', { project })
ontogglepublish?.(new CustomEvent('togglepublish', { detail: { project } }))
}
function handleDelete() {
dispatch('delete', { project })
ondelete?.(new CustomEvent('delete', { detail: { project } }))
}
function handleClickOutside() {

View file

@ -323,10 +323,10 @@
<AlbumListItem
{album}
isDropdownActive={activeDropdown === album.id}
on:toggleDropdown={handleToggleDropdown}
on:edit={handleEdit}
on:togglePublish={handleTogglePublish}
on:delete={handleDelete}
ontoggledropdown={handleToggleDropdown}
onedit={handleEdit}
ontogglepublish={handleTogglePublish}
ondelete={handleDelete}
/>
{/each}
</div>

View file

@ -133,7 +133,7 @@ const statusFilterOptions = [
initialMode="page"
initialPostType="post"
closeOnSave={false}
on:saved={handleComposerSaved}
onsaved={handleComposerSaved}
/>
</div>
{/if}
@ -186,9 +186,9 @@ const statusFilterOptions = [
{#each filters.items as post (post.id)}
<PostListItem
{post}
on:edit={handleEdit}
on:togglePublish={handleTogglePublish}
on:delete={handleDelete}
onedit={handleEdit}
ontogglepublish={handleTogglePublish}
ondelete={handleDelete}
/>
{/each}
</div>

View file

@ -168,9 +168,9 @@
{#each filters.items as project (project.id)}
<ProjectListItem
{project}
on:edit={handleEdit}
on:togglePublish={handleTogglePublish}
on:delete={handleDelete}
onedit={handleEdit}
ontogglepublish={handleTogglePublish}
ondelete={handleDelete}
/>
{/each}
</div>