-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Closed
Labels
Description
Describe the bug
The video collapsible component below works just fine in Svelte 4, but completely breaks in Svelte 5. The only change to the component is the syntax to pass the props to the component. This is a major blocker as it completely breaks the page that the component is on, resulting in a blank page with no information at all.
Testing the component in the playground, the console says there is a double unmount of the component, but I have no idea why or how.
Reproduction
The svelte component in question:
<!-- The component to create the video collapsible -->
<script lang="ts">
//
// The interface for the props passed to the component
interface Props {
//
// The variable to take in the list of videos
videos:
| [string, string][]
| {
[key: string]: string;
};
// The variable to take in the title for the video collapsible
title?: string;
}
// The type of event for the loadEmbeds function
type LoadEmbedsEvent = MouseEvent & { currentTarget: HTMLElement };
// Get the video and the title from the props
let { videos, title = "View the videos" }: Props = $props();
// Initialise the list of videos
const listOfVideos: [string, string][] = Array.isArray(videos)
? videos
: Object.entries(videos);
// The regex to remove everything but the YouTube ID
const youtubeIdRegex = /^.*\/(?:watch\?v=)?|[?&].+$/g;
// The regex to remove everything but the YouTube timestamp
const youtubeTimestampRegex = /^.*t=|s+$/g;
// The function to run a function once
function once(fn: ((event: LoadEmbedsEvent) => void) | null) {
return function (this: typeof fn, event: LoadEmbedsEvent) {
if (fn) fn.call(this, event);
fn = null;
};
}
// Function to get the ID of a YouTube video
function getYoutubeId(youtubeVideoUrl: string) {
return youtubeVideoUrl.replace(youtubeIdRegex, "").trim();
}
// Function to get the timestamp of a YouTube video
function getYoutubeTimestamp(youtubeVideoUrl: string): number {
//
// Gets the string of the timestamp for the YouTube video
const timestampString = youtubeVideoUrl
.replace(youtubeTimestampRegex, "")
.trim();
// Returns 0 if the timestamp string doesn't exist
// Otherwise, returns the timestamp string converted to a number
return timestampString === "" ? 0 : parseInt(timestampString);
}
// Function to load all the embeds inside the video collapsible
// when the collapsible is opened
function loadEmbeds(e: MouseEvent & { currentTarget: HTMLElement }) {
//
// Gets the parent element
const parentElement = e.currentTarget.parentElement;
// Exits the function if the parent element is not defined
if (parentElement == null) return;
// Gets all the iframe elements in the parent element
const iframeElements = parentElement.getElementsByTagName("iframe");
// Iterates over all the iframe elements and set their src attribute
for (const iframeElement of iframeElements)
iframeElement.src = iframeElement.dataset.src!;
}
</script>
<!-- The HTML for the video collapsible -->
<details>
<summary {title} onclick={once(loadEmbeds)}>
<div class="text">{listOfVideos.length} videos</div>
<div class="icon-wrapper">
<div class="plus-icon">
<div class="vertical-bar"></div>
<div class="horizontal-bar"></div>
</div>
</div>
</summary>
<section class="video-collapsible">
<!-->
<!-- Iterates over the list of videos -->
{#each listOfVideos as [videoInfo, url]}
{@const youtubeId = getYoutubeId(url)}
{@const youtubeTimestamp = getYoutubeTimestamp(url)}
<!-- Display the YouTube embed -->
<iframe
data-src={`https://www.youtube-nocookie.com/embed/${youtubeId}?start=${youtubeTimestamp}`}
src=""
title={videoInfo}
frameborder="0"
allow="clipboard-write; encrypted-media; picture-in-picture; web-share"
allowfullscreen
>
<a href={url} target="_blank" title={videoInfo}>{videoInfo}</a>
</iframe>
{/each}
</section>
</details>
<!-- The styles for the video collapsible -->
<style>
details {
--collapsible-label-padding: 1em;
}
summary {
display: flex;
flex-direction: row;
align-items: center;
padding: var(--collapsible-label-padding);
cursor: pointer;
background-color: var(--accent-colour);
}
summary:hover {
background-color: var(--accent-hover-colour);
}
.icon-wrapper {
flex: 1;
display: flex;
align-items: center;
justify-content: end;
}
/* The "+" icon at the end of the video collapsible */
/* Reference: https://jsfiddle.net/psullivan6/0eL3jezk/ */
.plus-icon {
--icon-size: 1.2em;
--icon-margin: 0.25em;
--bar-width: 0.25em;
position: relative;
width: var(--icon-size);
height: var(--icon-size);
justify-self: end;
margin-right: var(--icon-margin);
margin-bottom: var(--icon-margin);
}
/* Vertical line of the "+" symbol */
.vertical-bar {
top: 0;
left: 50%;
width: var(--bar-width);
height: 100%;
margin-top: 2px;
}
/* Horizontal line of the "+" symbol */
.horizontal-bar {
top: 50%;
left: 0;
height: var(--bar-width);
width: 100%;
margin-left: 2px;
}
.vertical-bar,
.horizontal-bar {
position: absolute;
background-color: var(--icon-colour);
transition: rotate var(--animation-timing);
}
.video-collapsible {
padding: 0 var(--collapsible-label-padding);
background-color: var(--collapsible-background-colour);
}
iframe {
width: 100%;
height: 100%;
aspect-ratio: 16/9;
margin: 2em 0;
}
/* Styles for when the collapsible is open */
details[open] > summary {
background-color: var(--accent-active-colour);
}
details[open] .vertical-bar {
rotate: 90deg;
}
details[open] .horizontal-bar {
rotate: 180deg;
}
/* The styles for mobile devices */
@media only screen and (max-width: 700px) {
iframe {
margin: 1.25em 0;
}
}
</style>Some test data to pass to the component:
let videos = ["4K Sample Video | Alpha 6700 | Sony | α", "https://youtu.be/O5O3yK8DJCc"];Full repository (navigate to the skate recommendations page):
https://github.com/hankertrix/Inline-Skate-Info/tree/migrate-to-svelte-5
Logs
Uncaught TypeError: element2.setAttribute is not a function
in {expression}
in VideoCollapsible.svelte
in TricksSection.svelte
in TricksPage.svelte
in +page.svelte
in PagefindHighlightLoader.svelte
in +layout.svelte
in root.svelte
set_attribute http://localhost:5173/node_modules/.vite/deps/chunk-PGMNWDSX.js?v=8f47ecb8:1452
VideoCollapsible http://localhost:5173/src/lib/components/general/VideoCollapsible.svelte:135
update_reaction http://localhost:5173/node_modules/.vite/deps/chunk-IXLKCLCE.js?v=8f47ecb8:1830
update_effect http://localhost:5173/node_modules/.vite/deps/chunk-IXLKCLCE.js?v=8f47ecb8:1921
create_effect http://localhost:5173/node_modules/.vite/deps/chunk-IXLKCLCE.js?v=8f47ecb8:1242
block http://localhost:5173/node_modules/.vite/deps/chunk-IXLKCLCE.js?v=8f47ecb8:1387
template_effect http://localhost:5173/node_modules/.vite/deps/chunk-IXLKCLCE.js?v=8f47ecb8:1384
VideoCollapsible http://localhost:5173/src/lib/components/general/VideoCollapsible.svelte:132
e http://localhost:5173/node_modules/.vite/deps/chunk-PGMNWDSX.js?v=8f47ecb8:975
update_reaction http://localhost:5173/node_modules/.vite/deps/chunk-IXLKCLCE.js?v=8f47ecb8:1830System Info
System:
OS: Linux 6.12 EndeavourOS
CPU: (16) x64 AMD Ryzen 9 7940HS w/ Radeon 780M Graphics
Memory: 10.61 GB / 14.85 GB
Container: Yes
Shell: 5.2.37 - /bin/bash
Binaries:
Node: 23.1.0 - /usr/bin/node
npm: 10.9.0 - /usr/bin/npm
pnpm: 9.14.4 - /usr/bin/pnpm
Browsers:
Chromium: 131.0.6778.85
npmPackages:
svelte: ^4.2.19 => 5.7.1Severity
blocking an upgrade