The Editor component is now implemented! Check it out.
v2.1.16
/
  • Get Started
  • Components
  • Composables
  • Typography
  • GitHub
  • Layout
  • App
  • Container
  • Error
  • SidebarLayout
  • Element
  • Advice
  • Alert
  • Avatar
  • AvatarGroup
  • Badge
  • Banner
  • Button
  • Calendar
  • Card
  • Chip
  • Collapsible
  • Countdown
  • FieldGroup
  • Kbd
  • Progress
  • Separator
  • Skeleton
  • Form
  • Checkbox
  • CheckboxGroup
  • ColorPicker
  • FileUpload
  • Form
  • FormField
  • Input
  • InputDate
  • InputMenu
  • InputNumber
  • InputTags
  • InputTime
  • PinInput
  • RadioGroup
  • Range
  • Select
  • SelectMenu
  • Switch
  • Textarea
  • Data
  • Accordion
  • DescriptionList
  • Empty
  • Table
  • TableWrapper
  • Timeline
  • User
  • Navigation
  • Breadcrumb
  • CommandPalette
  • Link
  • NavigationMenu
  • Pagination
  • Stepper
  • Tabs
  • Overlay
  • ContextMenu
  • DropdownMenu
  • Modal
  • Popover
  • Slideover
  • Toast
  • Tooltip
  • Page
  • PageCard
  • PageColumns
  • PageGrid
  • PageLinks
  • PageList
  • Dashboard
  • DashboardGroup
  • DashboardSearch
  • DashboardSearchButton
  • AI Chat
  • soonChatMessage
  • soonChatMessages
  • soonChatPalette
  • soonChatPrompt
  • soonChatPromptSubmit
  • Editor
  • NewEditor
  • NewEditorDragHandle
  • NewEditorEmojiMenu
  • NewEditorMentionMenu
  • NewEditorSuggestionMenu
  • NewEditorToolbar
  • Content
  • ContentSearch
  • ContentSearchButton
  • ContentSurround
  • ContentToc
  • Color Mode
  • ColorModeAvatar
  • ColorModeButton
  • ColorModeImage
  • ColorModeSelect
  • ColorModeSwitch
  • i18n
  • LocaleSelect
  • b24icons
  • b24jssdk
Use our Nuxt starter
v2.1.16
  • Docs
  • Components
  • Composables
  • Typography

EditorDragHandle

A draggable handle component for reordering and selecting editor blocks.
GitHub
Demo
Nuxt UI

Usage

The EditorDragHandle component provides drag-and-drop functionality for reordering editor blocks using the @tiptap/extension-drag-handle-vue-3 package.

It must be used inside an Editor component's default slot to have access to the editor instance.

It extends the Button component, so you can pass any property such as color, size, etc.

<script setup lang="ts">
const value = ref(`# Drag Handle

Hover over the left side of this block to see the drag handle appear and reorder blocks.`)
</script>

<template>
  <B24Editor v-slot="{ editor }" v-model="value" content-type="markdown" class="w-full min-h-21">
    <B24EditorDragHandle :editor="editor" />
  </B24Editor>
</template>
Learn more about the Drag Handle extension in the TipTap documentation.

Icon

Use the icon prop to customize the drag handle icon.

<script setup lang="ts">
import DragLIcon from '@bitrix24/b24icons-vue/outline/DragLIcon'
</script>

<template>
  <B24Editor v-slot="{ editor }">
    <B24EditorDragHandle :editor="editor" :icon="DragLIcon" />
  </B24Editor>
</template>

Options

Use the options prop to customize the positioning behavior using Floating UI options.

The offset is automatically calculated to center the handle for small blocks and align it to the top for taller blocks.
<template>
  <B24Editor v-slot="{ editor }">
    <B24EditorDragHandle
      :editor="editor"
      :options="{
        placement: 'left'
      }"
    />
  </B24Editor>
</template>

Examples

With dropdown menu

Use the default slot to add a DropdownMenu with block-level actions like duplicate, delete, move up/down, or transform blocks into different types.

Listen to the @node-change event to track the currently hovered node and its position, then use editor.chain().setMeta('lockDragHandle', open).run() to lock the handle position while the menu is open.

<script setup lang="ts">
import { upperFirst } from 'scule'
import type { DropdownMenuItem } from '@bitrix24/b24ui-nuxt'
import { mapEditorItems } from '@bitrix24/b24ui-nuxt/utils/editor'
import type { Editor, JSONContent } from '@tiptap/vue-3'
import DragLIcon from '@bitrix24/b24icons-vue/outline/DragLIcon'
import RepeatIcon from '@bitrix24/b24icons-vue/outline/RepeatIcon'
import ResumeIcon from '@bitrix24/b24icons-vue/outline/ResumeIcon'
import TextIcon from '@bitrix24/b24icons-vue/outline/TextIcon'
import BulletedListIcon from '@bitrix24/b24icons-vue/outline/BulletedListIcon'
import NumberedListIcon from '@bitrix24/b24icons-vue/outline/NumberedListIcon'
import QuoteIcon from '@bitrix24/b24icons-vue/outline/QuoteIcon'
import CodeIcon from '@bitrix24/b24icons-vue/common-service/CodeIcon'
import CopyIcon from '@bitrix24/b24icons-vue/outline/CopyIcon'
import CopyFileIcon from '@bitrix24/b24icons-vue/crm/CopyFileIcon'
import ArrowTopLIcon from '@bitrix24/b24icons-vue/outline/ArrowTopLIcon'
import ArrowDownLIcon from '@bitrix24/b24icons-vue/outline/ArrowDownLIcon'
import TrashcanIcon from '@bitrix24/b24icons-vue/outline/TrashcanIcon'

const value = ref(`Hover over the left side to see both drag handle and menu button.

Click the menu to see block actions. Try duplicating or deleting a block.`)

const selectedNode = ref<{ node: JSONContent, pos: number }>()

const items = (editor: Editor): DropdownMenuItem[][] => {
  if (!selectedNode.value?.node?.type) {
    return []
  }

  return mapEditorItems(editor, [
    [
      {
        type: 'label',
        label: upperFirst(selectedNode.value.node.type)
      },
      {
        label: 'Turn into',
        icon: RepeatIcon,
        children: [
          { kind: 'paragraph', label: 'Paragraph', icon: TextIcon },
          { kind: 'heading', level: 1, label: 'Heading 1' },
          { kind: 'heading', level: 2, label: 'Heading 2' },
          { kind: 'heading', level: 3, label: 'Heading 3' },
          { kind: 'heading', level: 4, label: 'Heading 4' },
          { kind: 'bulletList', label: 'Bullet List', icon: BulletedListIcon },
          { kind: 'orderedList', label: 'Ordered List', icon: NumberedListIcon },
          { kind: 'blockquote', label: 'Blockquote', icon: QuoteIcon },
          { kind: 'codeBlock', label: 'Code Block', icon: CodeIcon }
        ]
      },
      {
        kind: 'clearFormatting',
        pos: selectedNode.value?.pos,
        label: 'Reset formatting',
        icon: ResumeIcon
      }
    ],
    [
      {
        kind: 'duplicate',
        pos: selectedNode.value?.pos,
        label: 'Duplicate',
        icon: CopyIcon
      },
      {
        label: 'Copy to clipboard',
        icon: CopyFileIcon,
        onSelect: async () => {
          if (!selectedNode.value) return

          const pos = selectedNode.value.pos
          const node = editor.state.doc.nodeAt(pos)
          if (node) {
            await navigator.clipboard.writeText(node.textContent)
          }
        }
      }
    ],
    [
      {
        kind: 'moveUp',
        pos: selectedNode.value?.pos,
        label: 'Move up',
        icon: ArrowTopLIcon
      },
      {
        kind: 'moveDown',
        pos: selectedNode.value?.pos,
        label: 'Move down',
        icon: ArrowDownLIcon
      }
    ],
    [
      {
        kind: 'delete',
        pos: selectedNode.value?.pos,
        label: 'Delete',
        icon: TrashcanIcon
      }
    ]
  ]) as DropdownMenuItem[][]
}
</script>

<template>
  <B24Editor
    v-slot="{ editor }"
    v-model="value"
    content-type="markdown"
    class="w-full min-h-19"
  >
    <B24EditorDragHandle v-slot="{ b24ui }" :editor="editor" @node-change="selectedNode = $event">
      <B24DropdownMenu
        v-slot="{ open }"
        :modal="false"
        :items="items(editor)"
        :content="{ side: 'left' }"
        :b24ui="{ content: 'w-48', label: 'text-xs' }"
        @update:open="editor.chain().setMeta('lockDragHandle', $event).run()"
      >
        <B24Button
          :icon="DragLIcon"
          color="air-tertiary"
          active-color="air-secondary"
          size="sm"
          :active="open"
          :class="b24ui.handle()"
        />
      </B24DropdownMenu>
    </B24EditorDragHandle>
  </B24Editor>
</template>
This example uses the mapEditorItems utility from @bitrix24/b24ui-nuxt/utils/editor to automatically map handler kinds (like duplicate, delete, moveUp, etc.) to their corresponding editor commands with proper state management.

With suggestion menu

Use the default slot to add a Button next to the drag handle to open the EditorSuggestionMenu.

Call the onClick slot function to get the current node position, then use handlers.suggestion?.execute(editor, { pos: node?.pos }).run() to insert new blocks at that position.

<script setup lang="ts">
import type { EditorSuggestionMenuItem } from '@bitrix24/b24ui-nuxt'
import BulletedListIcon from '@bitrix24/b24icons-vue/outline/BulletedListIcon'
import QuoteIcon from '@bitrix24/b24icons-vue/outline/QuoteIcon'
import PlusLIcon from '@bitrix24/b24icons-vue/outline/PlusLIcon'
import DragLIcon from '@bitrix24/b24icons-vue/outline/DragLIcon'

const value = ref(`Click the plus button to open the suggestion menu and add new blocks.

The button appears when hovering over blocks.`)

const suggestionItems: EditorSuggestionMenuItem[][] = [
  [
    {
      kind: 'heading',
      level: 1,
      label: 'Heading 1'
    },
    {
      kind: 'heading',
      level: 2,
      label: 'Heading 2'
    },
    {
      kind: 'bulletList',
      label: 'Bullet List',
      icon: BulletedListIcon
    },
    {
      kind: 'blockquote',
      label: 'Blockquote',
      icon: QuoteIcon
    }
  ]
]
</script>

<template>
  <B24Editor
    v-slot="{ editor, handlers }"
    v-model="value"
    content-type="markdown"
    class="w-full min-h-35"
    :b24ui="{ base: 'p-8 sm:px-16' }"
  >
    <B24EditorDragHandle v-slot="{ b24ui, onClick }" :editor="editor">
      <B24Button
        :icon="PlusLIcon"
        color="air-tertiary"
        size="sm"
        :class="b24ui.handle()"
        @click="(e) => {
          e.stopPropagation()

          const selected = onClick()
          handlers.suggestion?.execute(editor, { pos: selected?.pos }).run()
        }"
      />

      <B24Button
        color="air-tertiary"
        size="sm"
        :icon="DragLIcon"
        :class="b24ui.handle()"
      />
    </B24EditorDragHandle>

    <B24EditorSuggestionMenu :editor="editor" :items="suggestionItems" />
  </B24Editor>
</template>

API

Props

Prop Default Type
as'button'any

The element or component this component should render as when not a link.

editorEditor
iconicons.dragIconComponent
color'air-tertiary-no-accent'"air-primary" | "air-primary-success" | "air-primary-alert" | "air-primary-copilot" | "air-secondary" | "air-secondary-alert" | "air-secondary-accent" | "air-secondary-accent-1" | "air-secondary-accent-2" | "air-secondary-no-accent" | "air-tertiary" | "air-tertiary-accent" | "air-tertiary-no-accent" | "air-selection" | "air-boost" | "link"
options{ strategy: 'absolute', placement: 'left-start' } FloatingUIOptions

The options for positioning the drag handle. Those are passed to Floating UI and include options for the placement, offset, flip, shift, size, autoPlacement, hide, and inline middleware.

  • strategy?: Strategy
  • placement?: Placement
  • offset?: boolean | OffsetOptions
  • flip?: boolean | { mainAxis?: boolean ; crossAxis?: boolean | "alignment" | undefined; fallbackPlacements?: Placement[] | undefined; fallbackStrategy?: "bestFit" | "initialPlacement" | undefined; fallbackAxisSideDirection?: "end" | "start" | "none" | undefined; flipAlignment?: boolean | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; padding?: Padding | undefined; boundary?: Boundary | undefined; } | undefined
  • shift?: boolean | { mainAxis?: boolean ; crossAxis?: boolean | undefined; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; padding?: Padding | undefined; limiter?: { fn: (state: MiddlewareState) => Coords; options?: any; } | undefined; boundary?: Boundary | undefined; } | undefined
  • size?: boolean | { rootBoundary?: RootBoundary ; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; padding?: Padding | undefined; boundary?: Boundary | undefined; apply?: ((args: { x: number; y: number; initialPlacement: Placement; placement: Placement; strategy: Strategy; middlewareData: MiddlewareData; rects: ElementRects; platform: Platform; elements: Elements; } & { availableWidth: number; availableHeight: number; }) => Promisable<void>) | undefined; } | undefined
  • autoPlacement?: boolean | { crossAxis?: boolean ; rootBoundary?: RootBoundary | undefined; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; padding?: Padding | undefined; alignment?: Alignment | null | undefined; autoAlignment?: boolean | undefined; allowedPlacements?: Placement[] | undefined; boundary?: Boundary | undefined; } | undefined
  • hide?: boolean | { rootBoundary?: RootBoundary ; elementContext?: ElementContext | undefined; altBoundary?: boolean | undefined; padding?: Padding | undefined; strategy?: "referenceHidden" | "escaped" | undefined; boundary?: Boundary | undefined; } | undefined
  • inline?: boolean | InlineOptions
pluginKey string | PluginKey<any>
onElementDragStart (e: DragEvent): void
onElementDragEnd (e: DragEvent): void
getReferencedVirtualElement (): VirtualElement | null
avatar AvatarProps

Display an avatar on the left side.

  • as?: any

    The element or component this component should render as. Defaults to 'span'.

  • src?: string
  • alt?: string
  • icon?: IconComponent

    Display an icon on the left side.

  • text?: string
  • size?: "sm" | "2xs" | "xs" | "md" | "lg" | "xl" | "3xs" | "2xl" | "3xl"

    Defaults to 'md'.

  • chip?: boolean | ChipProps
  • class?: any
  • style?: any
  • b24ui?: { root?: ClassNameValue; image?: ClassNameValue; fallback?: ClassNameValue; icon?: ClassNameValue; }
  • referrerpolicy?: HTMLAttributeReferrerPolicy
  • loading?: "lazy" | "eager"
  • crossorigin?: "" | "anonymous" | "use-credentials"
  • decoding?: "async" | "auto" | "sync"
  • height?: Numberish
  • sizes?: string
  • srcset?: string
  • usemap?: string
  • width?: Numberish
loadingboolean

When true, the loading icon will be displayed.

autofocus false | true | "true" | "false"
disabledboolean
name string
type'button' "reset" | "submit" | "button"

The type of the button when not a link.

isActionboolean

When true, uses special underlined styling.

label string
activeColor"air-primary" | "air-primary-success" | "air-primary-alert" | "air-primary-copilot" | "air-secondary" | "air-secondary-alert" | "air-secondary-accent" | "air-secondary-accent-1" | "air-secondary-accent-2" | "air-secondary-no-accent" | "air-tertiary" | "air-tertiary-accent" | "air-tertiary-no-accent" | "air-selection" | "air-boost" | "link"
size'sm' "sm" | "xs" | "md" | "xss" | "lg" | "xl"
roundedfalseboolean

Rounds the corners of the button

blockfalseboolean

Render the button full width

loadingAutofalseboolean

Set loading state automatically based on the @click promise state

normalCasetrueboolean

Disable uppercase label

useWaitfalseboolean

Shows LoaderWaitIcon in loading mode

useClockfalseboolean

Shows LoaderClockIcon icon in loading mode

useDropdownfalseboolean

Shows icons.ChevronDownSIcon on the right side

b24ui { root?: ClassNameValue; handle?: ClassNameValue; } & { base?: ClassNameValue; baseLoading?: ClassNameValue; baseLoadingWaitIcon?: ClassNameValue; baseLoadingClockIcon?: ClassNameValue; baseLoadingSpinnerIcon?: ClassNameValue; baseLine?: ClassNameValue; label?: ClassNameValue; labelInner?: ClassNameValue; leadingIcon?: ClassNameValue; leadingAvatar?: ClassNameValue; leadingAvatarSize?: ClassNameValue; trailingIcon?: ClassNameValue; safeList?: ClassNameValue; }
  • root?: ClassNameValue
  • handle?: ClassNameValue
  • base?: ClassNameValue
  • baseLoading?: ClassNameValue
  • baseLoadingWaitIcon?: ClassNameValue
  • baseLoadingClockIcon?: ClassNameValue
  • baseLoadingSpinnerIcon?: ClassNameValue
  • baseLine?: ClassNameValue
  • label?: ClassNameValue
  • labelInner?: ClassNameValue
  • leadingIcon?: ClassNameValue
  • leadingAvatar?: ClassNameValue
  • leadingAvatarSize?: ClassNameValue
  • trailingIcon?: ClassNameValue
  • safeList?: ClassNameValue

Slots

Slot Type
default{ b24ui: object; }

Emits

Event Type
nodeChange[{ node: JSONContent; pos: number; }]

Theme

app.config.ts
export default defineAppConfig({
  b24ui: {
    editorDragHandle: {
      slots: {
        root: 'hidden sm:flex items-center justify-center transition-all duration-200 ease-out',
        handle: 'cursor-grab px-1'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import bitrix24UIPluginVite from '@bitrix24/b24ui-nuxt/vite'

export default defineConfig({
  plugins: [
    vue(),
    bitrix24UIPluginVite({
      b24ui: {
        editorDragHandle: {
          slots: {
            root: 'hidden sm:flex items-center justify-center transition-all duration-200 ease-out',
            handle: 'cursor-grab px-1'
          }
        }
      }
    })
  ]
})

Editor

A rich text editor built on TipTap supporting markdown, HTML, and JSON content formats.

EditorEmojiMenu

An emoji suggestion menu that automatically appears upon typing the colon : character in the editor.

On this page

  • Usage
    • Icon
    • Options
  • Examples
    • With dropdown menu
    • With suggestion menu
  • API
    • Props
    • Slots
    • Emits
  • Theme
Releases
Published under MIT License.

Copyright © 2024-present Bitrix24