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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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