This document outlines the breaking changes in @portabletext/svelte v1.0.0
with a guide on how to do the switch.
The goal of the changes is to make the module more ergonomic to use and better aligned with @portabletext/react. With a shared API and better portability between frameworks, we hope to get closer to PortableText's mission of being extremely portable and presentation-agnostic.
This package makes usage of the svelte:element
tag introduced in 3.47.0. It won't work with prior versions of Svelte.
// Before
import PortableText from '@portabletext/svelte'
// After (`^1.0.0`)
import {PortableText} from '@portabletext/svelte'
This component renders any Portable Text content or custom object (such as codeBlock
, mapLocation
or callToAction
). As blocks
is tightly coupled to text blocks, we've renamed the main input to value
.
<!-- From: -->
<PortableText
blocks={[
/* ... */
]}
/>
<!-- ✅ To: -->
<PortableText
value={[
/* ... */
]}
/>
"Serializers" are now named "Components", which should make their role as custom renderers of content more understandable for Svelte developers.
<!-- From: -->
<PortableText {/* ... */} serializers={{
marks: {/* ... */},
types: {/* ... */},
// ...
}} />
<!-- ✅ To: -->
<PortableText {/* ... */} components={{
marks: {/* ... */},
types: {/* ... */},
// ...
}} />
When customizing the rendering of text blocks according to their style, previously you'd add the custom components under the blockStyles
. That property is now renamed to block
:
<!-- From: -->
<PortableText {/* ... */} serializers={{
blockStyles: {
normal: CustomParagraph,
blockquote: Quote,
},
// ...
}} />
<!-- ✅ To: -->
<PortableText {/* ... */} components={{
block: {
normal: CustomParagraph,
blockquote: Quote,
},
// ...
}} />
💡 Bonus: block
can also be a single Svelte component that is used for all text blocks regardless of their style
. One example would be the DefaultBlock.svelte component this library uses - it handles all default block types.
Previously, you'd specify the rendering of lists & list items inside the blockStyles
value (now named blocks
). Now each have their own top-level property:
<!-- From: -->
<PortableText {/* ... */} serializers={{
blockStyles: {
list_bullet: UnorderedListWrapper,
list_number: OrderedListWrapper,
listItem_bullet: ListItem,
listItem_number: ListItem,
},
// ...
}} />
<!-- ✅ To: -->
<PortableText {/* ... */} components={{
list: {
// No more list_ prefix
bullet: UnorderedListWrapper,
number: OrderedListWrapper,
},
listItem: {
bullet: ListItem,
number: ListItem,
}
// ...
}} />
With the change above, you can also define components for custom lists:
<script>
import {DefaultList} from '@portabletext/svelte'
</script>
<PortableText {/* ... */} components={{
list: {
checklist: DefaultList,
},
listItem: {
checklist: ChecklistItem,
}
// ...
}} />
<br>
tags weren't previously rendered when a block would be split between hard breaks stored through \n
strings (see #2). That's now fixed in v1.
For simple link
mark annotations with an href
, link
or url
value, the renderer now provides a simplified default renderer.
Before, we were rendering them as u
elements, which have a different semantic meaning attached to them.
<!-- From: -->
<u>My rendered underline</u>
<!-- ✅ To: -->
<span style="text-decoration:underline;">My rendered underline</span>
You can now use the provided toPlainText
utility function to render PortableText in plain strings:
<script>
import {toPlainText} from '@portabletext/svelte'
</>
<svelte:head>
<meta name="og:description" value={toPlainText(myPortableTextData)} />
</svelte:head>
Previously, you'd use BlockProps
and MarkProps
for custom components in types
and blockStyles
(now named blocks
). Now, we have 4 new prop types exposed:
<script>
// From:
import {BlockProps, MarkProps} from '@portabletext/svelte'
// ✅ To:
import {
BlockComponentProps,
CustomBlockComponentProps,
MarkComponentProps,
ListComponentProps,
ListItemComponentProps
} from '@portabletext/svelte'
// Example usage:
export let portableText: CustomBlockComponentProps<{title: string; subtitle?: string}>
</script>
<script>
// From:
$: ({context} = portableText)
// ✅ To:
$: ({
global: {context}
} = portableText)
</script>
Refer to the example Footnote mark component for a reference of how it can be used.
Also extends to new list
and listItem
components.
<script>
// Marks, Blocks & CustomBlocks now receive `value` in portableText
export let portableText
$: ({value} = portableText)
</script>
Keep in mind that decorators - purely visual marks without an attached markDef
for custom data - won't have a value. Only annotations - marks with markDef
- will.
Marks keep receiving their parent block as a prop, but now under the parentBlock
key:
<!-- Example mark: internal link -->
<script>
// From:
import {MarkProps} from '@portabletext/svelte'
export let portableText: MarkProps<{linkedPageSlug: string}>
$: ({block} = portableText)
// ✅ To:
import {MarkComponentProps} from '@portabletext/svelte'
export let portableText: MarkComponentProps<{linkedPageSlug: string}>
$: ({parentBlock} = portableText)
// Example usage:
$: siblingLinks = parentBlock.markDefs.filter(
(def) => def._type == 'internalLink' && def._key !== portableText.mark._key
)
$: isSoleLinkInBlock = siblingLinks.length <= 0
</script>
This library now uses @portabletext/toolkit to handle its internals. One of the benefits of the toolkit is having
Useful when you need to use the mark's text content for accessibility, custom displays, etc.
<!-- Example usage: link w/ custom accessibility title -->
<script>
import {MarkComponentProps} from '@portabletext/svelte'
export let portableText: MarkComponentProps<{linkedPageSlug: string}>
$: ({value, plainTextContent} = portableText)
</script>
<a href={value.href} title={`Open "${plainTextContent}"`}>
<slot />
</a>
These are all of the changes introduced by v1. If you have questions, suggestions or bug reports, please open a new issue in this repository.