> For the complete documentation index, see [llms.txt](/llms.txt).
> The full corpus is at [llms-full.txt](/llms-full.txt).

# Behavior recipes

> Common solutions using the Behavior API.

import {CardGrid, LinkCard} from '@astrojs/starlight/components'
import {PackageManagers} from 'starlight-package-managers'

Below are some common behavior examples. You can also find a list of core behaviors [on GitHub](https://github.com/portabletext/editor/tree/main/packages/editor/src/behaviors).

:::note[Prerequisites]
These recipes are written for `@portabletext/editor` **v6.x** ([changelog](https://github.com/portabletext/editor/releases)).
:::

To add these to your editor, first import `defineBehavior` as well as the `BehaviorPlugin`.

```tsx
import {defineBehavior} from '@portabletext/editor/behaviors'
import {BehaviorPlugin} from '@portabletext/editor/plugins'
```

Then, register the behavior within the `EditorProvider` using the `BehaviorPlugin`.

```tsx
<EditorProvider
  initialConfig={{
    schemaDefinition,
  }}
>
  <BehaviorPlugin behaviors={[logInsertText]} />
  {/* ... */}
</EditorProvider>
```

Read more about using behaviors and building your own with these guides:

<CardGrid>
  <LinkCard
    title="Create a custom behavior"
    description="Learn to create and register a custom behavior"
    href="/editor/guides/create-behavior/"
  />
  <LinkCard
    title="Behaviors"
    description="Learn more about the Behavior API"
    href="/editor/concepts/behavior/"
  />
</CardGrid>

## Log inserted text

Send and `effect` type action along with a `forward` action to perform side effects without altering the chain of events.

```js
const logInsertText = defineBehavior({
  on: 'insert.text',
  actions: [
    ({event}) => [
      {
        type: 'effect',
        effect: () => {
          console.log(event)
        },
      },
      {
        type: 'forward',
        event,
      },
    ],
  ],
})
```

The `effect` and `forward` actions also have shorthand functions:

```tsx
const logInsertText = defineBehavior({
  on: 'insert.text',
  actions: [
    ({event}) => [
      effect(() => {
        console.log(event)
      }),
      forward(event),
    ],
  ],
})
```

## Auto-close parenthesis

You can write behaviors to auto-close common paired characters. This example closes parenthesis, and then moves the cursor in between the two characters. This logic can expand to cover more sets.

```js
const autoCloseParens = defineBehavior({
  on: 'insert.text',
  guard: ({snapshot, event}) => {
    return event.text === '('
  },
  actions: [
    ({snapshot, event}) => [
      // Execute the original event that includes the '('
      {type: 'execute', event},
      // Execute a new insert.text event with a closing parenthesis
      {
        type: 'execute',
        event: {
          type: 'insert.text',
          text: ')',
        },
      },
      // Execute a select event to move the cursor in between the parens
      {
        type: 'execute',
        event: {
          type: 'select',
          selection: {
            anchor: {
              path: snapshot.context.selection.anchor.path,
              offset: snapshot.context.selection.anchor.offset + 1,
            },
            focus: {
              path: snapshot.context.selection.focus.path,
              offset: snapshot.context.selection.focus.offset + 1,
            },
          },
        },
      },
    ],
  ],
})
```

The `execute` action also has a shorthand function:

```tsx
const autoCloseParens = defineBehavior({
  on: 'insert.text',
  guard: ({snapshot, event}) => {
    return event.text === '('
  },
  actions: [
    ({snapshot, event}) => [
      execute(event),
      // ...
    ],
  ],
})
```

## Emoji picker

An emoji picker that triggers when you insert `:` is available as a separate plugin package.

![Emoji picker in Portable Text Editor](../../../../assets/emoji-picker.png)

Test it out in the [Playground](https://playground.portabletext.org).

Install the package:

<PackageManagers pkg="@portabletext/plugin-emoji-picker" />

Use the `useEmojiPicker` hook to handle the state and logic:

```tsx
import {
  createMatchEmojis,
  useEmojiPicker,
} from '@portabletext/plugin-emoji-picker'

const matchEmojis = createMatchEmojis({
  emojis: {
    '😂': ['joy', 'laugh'],
    '😹': ['joy_cat'],
  },
})

function EmojiPickerComponent() {
  const {keyword, matches, selectedIndex, onDismiss, onNavigateTo, onSelect} =
    useEmojiPicker({matchEmojis})

  // Render your emoji picker UI using these values
}
```

- [View the plugin source](https://github.com/portabletext/editor/tree/main/packages/plugin-emoji-picker).
- [View the playground editor source](https://github.com/portabletext/editor/blob/main/apps/playground/src/editor.tsx).

## Raise events

Sometimes you want to trigger an event from within an action. This sends the event back to the editor, where the editor treats it like any other event.

```tsx
const raisedUppercaseA = defineBehavior({
  on: 'insert.text',
  guard: ({snapshot, event}) => event.text === 'a',
  actions: [
    ({snapshot, event}) => [
      {type: 'raise', event: {type: 'insert.text', text: 'A'}},
    ],
  ],
})
```

The `raise` action also has a shorthand function:

```tsx
const raisedUppercaseA = defineBehavior({
  on: 'insert.text',
  guard: ({snapshot, event}) => event.text === 'a',
  actions: [({snapshot, event}) => [raise({type: 'insert.text', text: 'A'})]],
})
```

:::note
Be careful when raising events, as this technique can lead to infinite loops if behaviors dispatch actions and events that trigger one another.
:::