formatCreateLink

How formatCreateLink inputType creates links and varies across browsers.

Overview

The formatCreateLink inputType is triggered when the user presses Ctrl/Cmd + K or uses the browser's link creation feature. The browser wraps selected text in an <a href="..."> element.

Basic Behavior

Scenario 1: Text selected, URL provided

Before (Text selected)

HTML:
<p>Visit Google now</p>

After Cmd/Ctrl + K (URL: https://google.com)

HTML:
<p>Visit <a href="https://google.com">Google</a> now</p>
Wraps selected text in <a> element

Scenario 2: Collapsed cursor (no text selected)

Before (Cursor position)

HTML:
<p>Visit Google| now</p>

After Cmd/Ctrl + K (URL: https://google.com)

HTML:
<p>Visit Google<a href="https://google.com"></a> now</p>
Creates empty link at cursor position

Browser-Specific Behavior

  • Most browsers prompt for URL via a dialog when Cmd/Ctrl + K is pressed
  • If no URL is provided, some browsers create an empty link or do nothing
  • The beforeinput event may include the URL in e.data or e.dataTransfer

Editor-Specific Handling

Different editor frameworks handle link creation differently, as links are more complex than simple text formatting. Here's how major editors implement formatCreateLink:

Slate.js

Link Creation

Slate wraps selected text in a link element node:

    import { Editor, Transforms, Element } from 'slate';

element.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'formatCreateLink') {
    e.preventDefault();
    
    const url = e.dataTransfer?.getData('text/uri-list') || 
                prompt('Enter URL:') || '';
    
    if (!url) return;
    
    // Check if selection is already in a link
    const [match] = Editor.nodes(editor, {
      match: n => Element.isElement(n) && n.type === 'link',
    });
    
    if (match) {
      // Update existing link URL
      Transforms.setNodes(editor, { url }, { at: match[1] });
    } else {
      // Wrap selection in link
      Transforms.wrapNodes(editor, {
        type: 'link',
        url,
        children: [],
      }, { split: true });
    }
  }
});
  
  • Element node: Links are represented as element nodes, not marks.
  • wrapNodes: Uses Transforms.wrapNodes() to wrap selection in link element.
  • URL storage: URL is stored as a property on the link element node.
ProseMirror

Link Creation

ProseMirror uses mark system for links:

    import { toggleMark } from 'prosemirror-commands';
import { schema } from './schema';

view.dom.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'formatCreateLink') {
    e.preventDefault();
    const { state, dispatch } = view;
    
    const url = e.dataTransfer?.getData('text/uri-list') || 
                prompt('Enter URL:') || '';
    
    if (!url) return;
    
    // ProseMirror uses marks for links
    const linkMark = schema.marks.link.create({ href: url });
    const { from, to } = state.selection;
    
    const tr = state.tr.addMark(from, to, linkMark);
    dispatch(tr);
  }
});
  
  • Mark system: Links are represented as marks (e.g., schema.marks.link).
  • addMark: Uses tr.addMark() to apply link mark to selection.
  • URL in mark: URL is stored as an attribute on the link mark.
Draft.js

Link Creation

Draft.js uses entity system for links:

    import { EditorState, Modifier, RichUtils } from 'draft-js';
import { Entity } from 'draft-js';

element.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'formatCreateLink') {
    e.preventDefault();
    
    const url = e.dataTransfer?.getData('text/uri-list') || 
                prompt('Enter URL:') || '';
    
    if (!url) return;
    
    // Create entity for link
    const contentState = editorState.getCurrentContent();
    const contentStateWithEntity = contentState.createEntity(
      'LINK',
      'MUTABLE',
      { url }
    );
    const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
    
    // Apply entity to selection
    const newContentState = Modifier.applyEntity(
      contentStateWithEntity,
      editorState.getSelection(),
      entityKey
    );
    
    const newState = EditorState.push(
      editorState,
      newContentState,
      'apply-entity'
    );
    
    setEditorState(newState);
  }
});
  
  • Entity system: Links are represented as entities (e.g., 'LINK' entity type).
  • createEntity: Creates an entity with URL metadata.
  • applyEntity: Uses Modifier.applyEntity() to apply entity to selection.

Related resources