@portabletext/editor 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +228 -261
  2. package/lib/_chunks-cjs/behavior.core.cjs.map +1 -1
  3. package/lib/_chunks-cjs/selector.get-text-before.cjs.map +1 -1
  4. package/lib/_chunks-cjs/selector.is-selection-collapsed.cjs.map +1 -1
  5. package/lib/_chunks-es/behavior.core.js.map +1 -1
  6. package/lib/_chunks-es/selector.get-text-before.js.map +1 -1
  7. package/lib/_chunks-es/selector.is-selection-collapsed.js.map +1 -1
  8. package/lib/behaviors/index.cjs +40 -38
  9. package/lib/behaviors/index.cjs.map +1 -1
  10. package/lib/behaviors/index.d.cts +23 -23
  11. package/lib/behaviors/index.d.ts +23 -23
  12. package/lib/behaviors/index.js +40 -38
  13. package/lib/behaviors/index.js.map +1 -1
  14. package/lib/index.cjs +301 -351
  15. package/lib/index.cjs.map +1 -1
  16. package/lib/index.d.cts +123 -753
  17. package/lib/index.d.ts +123 -753
  18. package/lib/index.js +301 -351
  19. package/lib/index.js.map +1 -1
  20. package/lib/selectors/index.cjs.map +1 -1
  21. package/lib/selectors/index.d.cts +30 -30
  22. package/lib/selectors/index.d.ts +30 -30
  23. package/lib/selectors/index.js.map +1 -1
  24. package/package.json +10 -10
  25. package/src/behaviors/behavior.code-editor.ts +2 -2
  26. package/src/behaviors/behavior.core.ts +2 -2
  27. package/src/behaviors/behavior.emoji-picker.ts +52 -41
  28. package/src/behaviors/behavior.links.ts +2 -2
  29. package/src/behaviors/behavior.markdown.ts +2 -2
  30. package/src/behaviors/behavior.types.ts +10 -10
  31. package/src/editor/PortableTextEditor.tsx +2 -0
  32. package/src/editor/__tests__/self-solving.test.tsx +14 -0
  33. package/src/editor/components/Synchronizer.tsx +6 -1
  34. package/src/editor/create-editor.ts +14 -3
  35. package/src/editor/define-schema.ts +4 -4
  36. package/src/editor/editor-event-listener.tsx +1 -1
  37. package/src/editor/editor-machine.ts +42 -52
  38. package/src/editor/editor-provider.tsx +3 -3
  39. package/src/editor/editor-selector.ts +31 -14
  40. package/src/editor/editor-snapshot.ts +2 -2
  41. package/src/editor/get-value.ts +12 -5
  42. package/src/editor/hooks/usePortableTextEditor.ts +1 -0
  43. package/src/editor/hooks/usePortableTextEditorSelection.tsx +1 -0
  44. package/src/selectors/selector.get-active-list-item.ts +1 -1
  45. package/src/selectors/selector.get-active-style.ts +1 -1
  46. package/src/selectors/selector.get-selected-spans.ts +1 -1
  47. package/src/selectors/selector.get-selection-text.ts +1 -1
  48. package/src/selectors/selector.get-text-before.ts +1 -1
  49. package/src/selectors/selector.is-active-annotation.ts +1 -1
  50. package/src/selectors/selector.is-active-decorator.ts +1 -1
  51. package/src/selectors/selector.is-active-list-item.ts +1 -1
  52. package/src/selectors/selector.is-active-style.ts +1 -1
  53. package/src/selectors/selector.is-selection-collapsed.ts +1 -1
  54. package/src/selectors/selector.is-selection-expanded.ts +1 -1
  55. package/src/selectors/selectors.ts +13 -13
  56. package/src/types/editor.ts +2 -2
package/README.md CHANGED
@@ -9,23 +9,65 @@
9
9
 
10
10
  > The official editor for editing [Portable Text](https://github.com/portabletext/portabletext) – the JSON based rich text specification for modern content editing platforms.
11
11
 
12
- ## Build Your Own Portable Text Editor
12
+ ## Get started with the Portable Text Editor
13
13
 
14
- Check [/examples/basic/src/App.tsx](/examples/basic/src/App.tsx) for a basic example of how to set up the edior. Most of the source code from this example app can also be found in the instructions below.
14
+ This library provides you with the building blocks to create a completely custom editor experience built on top of Portable Text. We recommend [checking out the official documentation](https://www.portabletext.org/). The following guide includes the basics to get your started.
15
+
16
+ In order to set up an editor you'll need to:
17
+
18
+ - Create a schema that defines the rich text and block content elements.
19
+ - Create a toolbar to toggle and insert these elements.
20
+ - Write render functions to style and display each element type in the editor.
21
+ - Render the editor.
22
+
23
+ Check out [example application](/examples/basic/src/App.tsx) in this repo for a basic implementation of the editor. Most of the source code from this example app can also be found in the instructions below.
24
+
25
+ ### Add the library to your project
26
+
27
+ ```sh
28
+ # npm
29
+ npm i @portabletext/editor
30
+
31
+ # pnpm
32
+ pnpm add @portabletext/editor
33
+
34
+ # yarn
35
+ yarn add @portabletext/editor
36
+
37
+ ```
38
+
39
+ Next, in your app or the component you're building, import `EditorProvider`, `EditorEventListener`, `PortableTextEditable`, `defineSchema`, and the types in the code below.
40
+
41
+ ```tsx
42
+ // App.tsx
43
+ import {
44
+ defineSchema,
45
+ EditorEventListener,
46
+ EditorProvider,
47
+ PortableTextEditable,
48
+ } from '@portabletext/editor'
49
+ import type {
50
+ PortableTextBlock,
51
+ RenderDecoratorFunction,
52
+ RenderStyleFunction,
53
+ } from '@portabletext/editor'
54
+ ```
15
55
 
16
56
  ### Define the Schema
17
57
 
18
- The first thing to do is to define the editor schema definition. The schema definition is later passed into the editor where it's compiled and used in various callbacks and render functions.
58
+ Before you can render the editor, you need a schema. The editor schema configures the types of content rendered by the editor.
59
+
60
+ We'll start with a schema that includes some common rich text elements.
61
+
62
+ > [!NOTE]
63
+ > This guide includes a limited set of schema types, or rich text elements, to get you started. See the [rendering guide](https://www.portabletext.org/guides/custom-rendering/) for additional examples.
19
64
 
20
- ```ts
21
- // All options are optional
22
- // Only the `name` property is required, but you can define a `title` and an `icon` as well
23
- // You can use this schema definition later to build your toolbar
65
+ ```tsx
66
+ // App.tsx
67
+ // ...
24
68
  const schemaDefinition = defineSchema({
25
69
  // Decorators are simple marks that don't hold any data
26
70
  decorators: [{name: 'strong'}, {name: 'em'}, {name: 'underline'}],
27
- // Annotations are more complex marks that can hold data
28
- annotations: [{name: 'link'}],
29
71
  // Styles apply to entire text blocks
30
72
  // There's always a 'normal' style that can be considered the paragraph style
31
73
  styles: [
@@ -35,136 +77,90 @@ const schemaDefinition = defineSchema({
35
77
  {name: 'h3'},
36
78
  {name: 'blockquote'},
37
79
  ],
38
- // Lists apply to entire text blocks as well
39
- lists: [{name: 'bullet'}, {name: 'number'}],
40
- // Inline objects hold arbitrary data that can be inserted into the text
41
- inlineObjects: [{name: 'stock-ticker'}],
42
- // Block objects hold arbitrary data that live side-by-side with text blocks
43
- blockObjects: [{name: 'image'}],
80
+
81
+ // The types below are left empty for this example.
82
+ // See the rendering guide to learn more about each type.
83
+
84
+ // Annotations are more complex marks that can hold data (for example, hyperlinks).
85
+ annotations: [],
86
+ // Lists apply to entire text blocks as well (for example, bullet, numbered).
87
+ lists: [],
88
+ // Inline objects hold arbitrary data that can be inserted into the text (for example, custom emoji).
89
+ inlineObjects: [],
90
+ // Block objects hold arbitrary data that live side-by-side with text blocks (for example, images, code blocks, and tables).
91
+ blockObjects: [],
44
92
  })
45
93
  ```
46
94
 
47
- ### Render the Editor Component
95
+ Learn more about the different types that exist in schema in the [Portable Text Overview](https://www.portabletext.org/concepts/portabletext/).
96
+
97
+ ### Render the editor
48
98
 
49
- Use `EditorProvider` to configure an editor and use `EditorEventListener` to listen for `mutation` changes inside the editor so you can use and store the value produced.
99
+ With a schema defined, you have enough to render the editor. It won't do much yet, but you can confirm your progress.
100
+
101
+ Add `useState` from React, then scaffold out a basic application component. For example:
50
102
 
51
103
  ```tsx
104
+ // app.tsx
105
+ import {
106
+ defineSchema,
107
+ EditorEventListener,
108
+ EditorProvider,
109
+ PortableTextEditable,
110
+ } from '@portabletext/editor'
111
+ import type {
112
+ PortableTextBlock,
113
+ RenderDecoratorFunction,
114
+ RenderStyleFunction,
115
+ } from '@portabletext/editor'
116
+ import {useState} from 'react'
117
+
118
+ const schemaDefinition = defineSchema({
119
+ /* your schema from the previous step */
120
+ })
121
+
52
122
  function App() {
123
+ // Set up the initial state getter and setter. Leave the starting value as undefined for now.
53
124
  const [value, setValue] = useState<Array<PortableTextBlock> | undefined>(
54
- // Initial value
55
- () => [
56
- {
57
- _type: 'block',
58
- _key: keyGenerator(),
59
- children: [
60
- {_type: 'span', _key: keyGenerator(), text: 'Hello, '},
61
- {
62
- _type: 'span',
63
- _key: keyGenerator(),
64
- text: 'world!',
65
- marks: ['strong'],
66
- },
67
- ],
68
- },
69
- ],
125
+ undefined,
70
126
  )
71
127
 
72
128
  return (
73
129
  <>
74
- {/* Create an editor */}
75
130
  <EditorProvider
76
131
  initialConfig={{
77
132
  schemaDefinition,
78
133
  initialValue: value,
79
134
  }}
80
135
  >
81
- {/* Subscribe to editor changes */}
82
136
  <EditorEventListener
83
137
  on={(event) => {
84
138
  if (event.type === 'mutation') {
85
- setValue(event.snapshot)
139
+ setValue(event.value)
86
140
  }
87
141
  }}
88
142
  />
89
- {/* Toolbar needs to be rendered inside the `EditorProvider` component */}
90
- <Toolbar />
91
- {/* Component that controls the actual rendering of the editor */}
92
143
  <PortableTextEditable
144
+ // Add an optional style to see it more easily on the page
93
145
  style={{border: '1px solid black', padding: '0.5em'}}
94
- // Control how decorators are rendered
95
- renderDecorator={renderDecorator}
96
- // Control how annotations are rendered
97
- renderAnnotation={renderAnnotation}
98
- // Required to render block objects but also to make `renderStyle` take effect
99
- renderBlock={renderBlock}
100
- // Control how styles are rendered
101
- renderStyle={renderStyle}
102
- // Control how inline objects are rendered
103
- renderChild={renderChild}
104
- // Rendering lists is harder and most likely requires a fair amount of CSS
105
- // First, return the children like here
106
- // Next, look in the imported `editor.css` file to see how list styles are implemented
107
- renderListItem={(props) => <>{props.children}</>}
108
146
  />
109
147
  </EditorProvider>
110
- <pre style={{border: '1px dashed black', padding: '0.5em'}}>
111
- {JSON.stringify(value, null, 2)}
112
- </pre>
113
148
  </>
114
149
  )
115
150
  }
116
- ```
117
-
118
- ### Render Marks, Blocks and Objects
119
-
120
- All the different render functions passed to `PortableTextEditable` can be defined as stand-alone React components. Most of these are fairly straightforward to render because everything you need is provided via `props`. However, lists are a little special. Since Portable Text has no concept of block nesting, the easiest way get something looking like lists is with pure CSS. Head over to [/examples/basic/src/editor.css](/examples/basic/src/editor.css) for a full example.
121
-
122
- ```tsx
123
- const renderDecorator: RenderDecoratorFunction = (props) => {
124
- if (props.value === 'strong') {
125
- return <strong>{props.children}</strong>
126
- }
127
- if (props.value === 'em') {
128
- return <em>{props.children}</em>
129
- }
130
- if (props.value === 'underline') {
131
- return <u>{props.children}</u>
132
- }
133
- return <>{props.children}</>
134
- }
135
151
 
136
- const renderAnnotation: RenderAnnotationFunction = (props) => {
137
- if (props.schemaType.name === 'link') {
138
- return <span style={{textDecoration: 'underline'}}>{props.children}</span>
139
- }
152
+ export default App
153
+ ```
140
154
 
141
- return <>{props.children}</>
142
- }
155
+ Include the `App` component in your application and run it. You should see an outlined editor that accepts text, but doesn't do much else.
143
156
 
144
- const renderBlock: RenderBlockFunction = (props) => {
145
- if (props.schemaType.name === 'image' && isImage(props.value)) {
146
- return (
147
- <div
148
- style={{
149
- border: '1px dotted grey',
150
- padding: '0.25em',
151
- marginBlockEnd: '0.25em',
152
- }}
153
- >
154
- IMG: {props.value.src}
155
- </div>
156
- )
157
- }
157
+ ### Create render functions for schema elements
158
158
 
159
- return <div style={{marginBlockEnd: '0.25em'}}>{props.children}</div>
160
- }
159
+ At this point the PTE only has a schema, but it doesn't know how to render anything. Fix that by creating render functions for each property in the schema.
161
160
 
162
- function isImage(
163
- props: PortableTextBlock,
164
- ): props is PortableTextBlock & {src: string} {
165
- return 'src' in props
166
- }
161
+ Start by creating a render function for styles.
167
162
 
163
+ ```tsx
168
164
  const renderStyle: RenderStyleFunction = (props) => {
169
165
  if (props.schemaType.value === 'h1') {
170
166
  return <h1>{props.children}</h1>
@@ -180,216 +176,187 @@ const renderStyle: RenderStyleFunction = (props) => {
180
176
  }
181
177
  return <>{props.children}</>
182
178
  }
179
+ ```
183
180
 
184
- const renderChild: RenderChildFunction = (props) => {
185
- if (props.schemaType.name === 'stock-ticker' && isStockTicker(props.value)) {
186
- return (
187
- <span
188
- style={{
189
- border: '1px dotted grey',
190
- padding: '0.15em',
191
- }}
192
- >
193
- {props.value.symbol}
194
- </span>
195
- )
196
- }
181
+ Render functions all follow the same format.
197
182
 
198
- return <>{props.children}</>
199
- }
183
+ - They take in props and return JSX elements.
184
+ - They use the schema to make decisions.
185
+ - They return JSX and pass `children` as a fallback.
200
186
 
201
- function isStockTicker(
202
- props: PortableTextChild,
203
- ): props is PortableTextChild & {symbol: string} {
204
- return 'symbol' in props
187
+ With this in mind, continue for the remaining schema types.
188
+
189
+ Create a render function for decorators.
190
+
191
+ ```tsx
192
+ const renderDecorator: RenderDecoratorFunction = (props) => {
193
+ if (props.value === 'strong') {
194
+ return <strong>{props.children}</strong>
195
+ }
196
+ if (props.value === 'em') {
197
+ return <em>{props.children}</em>
198
+ }
199
+ if (props.value === 'underline') {
200
+ return <u>{props.children}</u>
201
+ }
202
+ return <>{props.children}</>
205
203
  }
206
204
  ```
207
205
 
208
- ### Render the Toolbar
206
+ > [!NOTE]
207
+ > By default, text is rendered as an inline `span` element in the editor. While most render functions return a fragment (`<>`) as the fallback, make sure block level elements return blocks, like `<div>` elements.
209
208
 
210
- Your toolbar needs to be rendered within `EditorProvider` because it requires a reference to the `editor` that it produces. To toggle marks and styles and to insert objects, you'll have to use the `.send` method on this `editor` instance.
209
+ Update the `PortableTextEditable` with each corresponding function to attach them to the editor.
210
+
211
+ You may notice that we skipped a few types from the schema. Declare these inline in the configuration, like in the code below. You can learn more about [customizing the render functions](https://www.portabletext.org/guides/custom-rendering/) in the documentation.
211
212
 
212
213
  ```tsx
213
- function Toolbar() {
214
- // Obtain the editor instance
215
- const editor = useEditor()
214
+ <PortableTextEditable
215
+ style={{border: '1px solid black', padding: '0.5em'}}
216
+ renderStyle={renderStyle}
217
+ renderDecorator={renderDecorator}
218
+ renderBlock={(props) => <div>{props.children}</div>}
219
+ renderListItem={(props) => <>{props.children}</>}
220
+ />
221
+ ```
216
222
 
217
- const decoratorButtons = schemaDefinition.decorators.map((decorator) => (
218
- <DecoratorButton key={decorator.name} decorator={decorator.name} />
219
- ))
223
+ Before you can see if anything changed, you need a way to interact with the editor.
220
224
 
221
- const annotationButtons = schemaDefinition.annotations.map((annotation) => (
222
- <AnnotationButton key={annotation.name} annotation={annotation} />
223
- ))
225
+ ### Create a toolbar
224
226
 
225
- const styleButtons = schemaDefinition.styles.map((style) => (
226
- <StyleButton key={style.name} style={style.name} />
227
- ))
227
+ A toolbar is a collection of UI elements for interacting with the editor. The PTE library gives you the necessary hooks to create a toolbar however you like. Learn more about [creating your own toolbar](https://www.portabletext.org/guides/customize-toolbar/) in the documentation.
228
228
 
229
- const listButtons = schemaDefinition.lists.map((list) => (
230
- <ListButton key={list.name} list={list.name} />
231
- ))
229
+ 1. Create a `Toolbar` component in the same file.
230
+ 2. Import the `useEditor` hook, and declare an `editor` constant in the component.
231
+ 3. Iterate over the schema types to create toggle buttons for each style and decorator.
232
+ 4. Send events to the editor to toggle the styles and decorators whenever the buttons are clicked.
233
+ 5. Render the toolbar buttons.
232
234
 
233
- const imageButton = (
235
+ ```tsx
236
+ // App.tsx
237
+ // ...
238
+ import {useEditor} from '@portabletext/editor'
239
+
240
+ function Toolbar() {
241
+ // useEditor provides access to the PTE
242
+ const editor = useEditor()
243
+
244
+ // Iterate over the schema (defined earlier), or manually create buttons.
245
+ const styleButtons = schemaDefinition.styles.map((style) => (
234
246
  <button
247
+ key={style.name}
235
248
  onClick={() => {
249
+ // Send style toggle event
236
250
  editor.send({
237
- type: 'insert.block object',
238
- blockObject: {
239
- name: 'image',
240
- value: {src: 'https://example.com/image.jpg'},
241
- },
242
- placement: 'auto',
251
+ type: 'style.toggle',
252
+ style: style.name,
253
+ })
254
+ editor.send({
255
+ type: 'focus',
243
256
  })
244
- editor.send({type: 'focus'})
245
257
  }}
246
258
  >
247
- {schemaDefinition.blockObjects[0].name}
259
+ {style.name}
248
260
  </button>
249
- )
261
+ ))
250
262
 
251
- const stockTickerButton = (
263
+ const decoratorButtons = schemaDefinition.decorators.map((decorator) => (
252
264
  <button
265
+ key={decorator.name}
253
266
  onClick={() => {
267
+ // Send decorator toggle event
268
+ editor.send({
269
+ type: 'decorator.toggle',
270
+ decorator: decorator.name,
271
+ })
254
272
  editor.send({
255
- type: 'insert.inline object',
256
- inlineObject: {
257
- name: 'stock-ticker',
258
- value: {symbol: 'AAPL'},
259
- },
273
+ type: 'focus',
260
274
  })
261
- editor.send({type: 'focus'})
262
275
  }}
263
276
  >
264
- {schemaDefinition.inlineObjects[0].name}
277
+ {decorator.name}
265
278
  </button>
266
- )
267
-
279
+ ))
268
280
  return (
269
281
  <>
270
- <div>{decoratorButtons}</div>
271
- <div>{annotationButtons}</div>
272
- <div>{styleButtons}</div>
273
- <div>{listButtons}</div>
274
- <div>{imageButton}</div>
275
- <div>{stockTickerButton}</div>
282
+ {styleButtons}
283
+ {decoratorButtons}
276
284
  </>
277
285
  )
278
286
  }
287
+ ```
279
288
 
280
- function DecoratorButton(props: {decorator: string}) {
281
- // Obtain the editor instance
282
- const editor = useEditor()
283
- // Check if the decorator is active using a selector
284
- const active = useEditorSelector(
285
- editor,
286
- selectors.isActiveDecorator(props.decorator),
287
- )
289
+ The `useEditor` hook gives you access to the active editor. `send` lets you send events to the editor. You can view the full list of events in the [Behavior API reference](https://www.portabletext.org/reference/behavior-api/).
288
290
 
289
- return (
290
- <button
291
- style={{
292
- textDecoration: active ? 'underline' : 'unset',
293
- }}
294
- onClick={() => {
295
- // Toggle the decorator
296
- editor.send({
297
- type: 'decorator.toggle',
298
- decorator: props.decorator,
299
- })
300
- // Pressing this button steals focus so let's focus the editor again
301
- editor.send({type: 'focus'})
302
- }}
303
- >
304
- {props.decorator}
305
- </button>
306
- )
307
- }
291
+ > [!NOTE]
292
+ > The example above sends a `focus` event after each action. Normally when interacting with a button, the browser removes focus from the text editing area. This event returns focus to the field to prevent interrupting the user.
308
293
 
309
- function AnnotationButton(props: {annotation: {name: string}}) {
310
- const editor = useEditor()
311
- const active = useEditorSelector(
312
- editor,
313
- selectors.isActiveAnnotation(props.annotation.name),
294
+ ### Bring it all together
295
+
296
+ With render functions created and a toolbar in place, you can fully render the editor. Add the `Toolbar` inside the `EditorProvider`.
297
+
298
+ ```tsx
299
+ // App.tsx
300
+ // ...
301
+ function App() {
302
+ const [value, setValue] = useState<Array<PortableTextBlock> | undefined>(
303
+ undefined,
314
304
  )
315
305
 
316
306
  return (
317
- <button
318
- style={{
319
- textDecoration: active ? 'underline' : 'unset',
320
- }}
321
- onClick={() => {
322
- editor.send({
323
- type: 'annotation.toggle',
324
- annotation: {
325
- name: props.annotation.name,
326
- value:
327
- props.annotation.name === 'link'
328
- ? {href: 'https://example.com'}
329
- : {},
330
- },
331
- })
332
- editor.send({type: 'focus'})
333
- }}
334
- >
335
- {props.annotation.name}
336
- </button>
307
+ <>
308
+ <EditorProvider
309
+ initialConfig={{
310
+ schemaDefinition,
311
+ initialValue: value,
312
+ }}
313
+ >
314
+ <EditorEventListener
315
+ on={(event) => {
316
+ if (event.type === 'mutation') {
317
+ setValue(event.value)
318
+ }
319
+ }}
320
+ />
321
+ <Toolbar />
322
+ <PortableTextEditable
323
+ style={{border: '1px solid black', padding: '0.5em'}}
324
+ renderStyle={renderStyle}
325
+ renderDecorator={renderDecorator}
326
+ renderBlock={(props) => <div>{props.children}</div>}
327
+ renderListItem={(props) => <>{props.children}</>}
328
+ />
329
+ </EditorProvider>
330
+ </>
337
331
  )
338
332
  }
333
+ // ...
334
+ ```
339
335
 
340
- function StyleButton(props: {style: string}) {
341
- const editor = useEditor()
342
- const active = useEditorSelector(editor, selectors.isActiveStyle(props.style))
336
+ You can now enter text and interact with the toolbar buttons to toggle the styles and decorators. These are only a small portion of the types of things you can do. Check out the [custom rendering guide](https://www.portabletext.org/guides/custom-rendering/) and the [toolbar customization guide](https://www.portabletext.org/guides/customize-toolbar/) for options.
343
337
 
344
- return (
345
- <button
346
- style={{
347
- textDecoration: active ? 'underline' : 'unset',
348
- }}
349
- onClick={() => {
350
- editor.send({type: 'style.toggle', style: props.style})
351
- editor.send({type: 'focus'})
352
- }}
353
- >
354
- {props.style}
355
- </button>
356
- )
357
- }
338
+ ### View the Portable Text data
358
339
 
359
- function ListButton(props: {list: string}) {
360
- const editor = useEditor()
361
- const active = useEditorSelector(
362
- editor,
363
- selectors.isActiveListItem(props.list),
364
- )
340
+ You can preview the Portable Text from the editor by reading the state. Add the following after the `EditorProvider`.
365
341
 
366
- return (
367
- <button
368
- style={{
369
- textDecoration: active ? 'underline' : 'unset',
370
- }}
371
- onClick={() => {
372
- editor.send({
373
- type: 'list item.toggle',
374
- listItem: props.list,
375
- })
376
- editor.send({type: 'focus'})
377
- }}
378
- >
379
- {props.list}
380
- </button>
381
- )
382
- }
342
+ ```tsx
343
+ <pre style={{border: '1px dashed black', padding: '0.5em'}}>
344
+ {JSON.stringify(value, null, 2)}
345
+ </pre>
383
346
  ```
384
347
 
385
- ## Behavior API (Coming Soon)
348
+ This displays the raw Portable Text. To customize how Portable Text renders in your apps, explore the [collection of serializers](https://www.portabletext.org/integrations/serializers/).
349
+
350
+ ## Behavior API
386
351
 
387
352
  The Behavior API is a new way of interfacing with the Portable Text Editor. It allows you to think of and treat the editor as a state machine by:
388
353
 
389
- 1. Declaratively hooking into editor **events** and defining new behaviors using `defineBehavior`. (A "Behavior" (1) listens for an **event**, (2) uses a **guard** to determine whether it should run and (3) raises a set of **actions** to be performed on the editor.)
390
- 2. Imperatively trigger **events** using `editor.send(…)` which in turn can trigger behaviors defined using `defineBehavior`.
391
- 3. Deriving editor **state** using **pure functions**.
392
- 4. Subscribe to **emitted** editor **events** using `editor.on(…)`.
354
+ - Declaratively hooking into editor **events** and defining new behaviors.
355
+ - Imperatively triggering **events**.
356
+ - Deriving editor **state** using **pure functions**.
357
+ - Subscribing to **emitted** editor **events**.
358
+
359
+ Learn more about the [Behaviors](https://www.portabletext.org/concepts/behavior/) and how to [create your own behaviors](https://www.portabletext.org/guides/create-behavior/) in the documentation.
393
360
 
394
361
  ## End-User Experience
395
362