Keyboard shortcuts extension for list operations in Tiptap rich text editor
npx @tessl/cli install tessl/npm-tiptap--extension-list-keymap@3.4.0This extension provides enhanced keyboard shortcuts for list operations in Tiptap rich text editors. It customizes the behavior of backspace and delete keys to improve list editing by properly joining paragraphs between list items instead of the default ProseMirror behavior of lifting or sinking items.
npm install @tiptap/extension-list-keymapimport { ListKeymap, listHelpers } from "@tiptap/extension-list-keymap";
import type { ListKeymapOptions } from "@tiptap/extension-list-keymap";For types and editor integration:
import { Editor } from "@tiptap/core";
import type { Extension } from "@tiptap/core";Default import:
import ListKeymap from "@tiptap/extension-list-keymap";CommonJS:
const { ListKeymap, listHelpers } = require("@tiptap/extension-list-keymap");import { Editor } from "@tiptap/core";
import { ListKeymap } from "@tiptap/extension-list-keymap";
const editor = new Editor({
extensions: [
ListKeymap.configure({
listTypes: [
{
itemName: 'listItem',
wrapperNames: ['bulletList', 'orderedList'],
},
{
itemName: 'taskItem',
wrapperNames: ['taskList'],
},
],
}),
],
});The main extension that registers custom keyboard shortcuts for enhanced list editing behavior.
const ListKeymap: Extension<ListKeymapOptions>;Keyboard Shortcuts Provided:
Delete: Handles forward deletion within list itemsMod-Delete: Handles modified forward deletion (Ctrl/Cmd + Delete)Backspace: Handles backward deletion within list itemsMod-Backspace: Handles modified backward deletion (Ctrl/Cmd + Backspace)The extension prevents default ProseMirror behavior where backspace/delete operations lift or sink list items when joining paragraphs. Instead, it joins paragraphs from two list items into a single list item for better user experience.
Configuration interface for customizing list types and behavior.
interface ListKeymapOptions {
/**
* An array of list types. This is used for item and wrapper list matching.
* @default [{ itemName: 'listItem', wrapperNames: ['bulletList', 'orderedList'] }, { itemName: 'taskItem', wrapperNames: ['taskList'] }]
*/
listTypes: Array<{
itemName: string;
wrapperNames: string[];
}>;
}Default Configuration:
listItem) with bullet and ordered liststaskItem) with task listsCollection of utility functions for advanced list operations and state checking.
namespace listHelpers {
/**
* Handles backspace key behavior in lists
* @param editor - The Tiptap editor instance
* @param name - The list item type name
* @param parentListTypes - Array of parent list type names
* @returns true if the keypress was handled, false otherwise
*/
function handleBackspace(editor: Editor, name: string, parentListTypes: string[]): boolean;
/**
* Handles delete key behavior in lists
* @param editor - The Tiptap editor instance
* @param name - The list item type name
* @returns true if the keypress was handled, false otherwise
*/
function handleDelete(editor: Editor, name: string): boolean;
/**
* Finds the position and depth of a list item in the document
* @param typeOrName - The node type or name to search for
* @param state - The current editor state
* @returns Object with resolved position and depth, or null if not found
*/
function findListItemPos(typeOrName: string | NodeType, state: EditorState): {
$pos: ResolvedPos;
depth: number;
} | null;
/**
* Gets the depth of the next list item
* @param typeOrName - The node type or name
* @param state - The current editor state
* @returns The depth number or false if not found
*/
function getNextListDepth(typeOrName: string, state: EditorState): number | false;
/**
* Checks if there's a list before the current position
* @param editorState - The current editor state
* @param name - The item type name
* @param parentListTypes - Array of parent list type names
* @returns true if a list exists before current position
*/
function hasListBefore(editorState: EditorState, name: string, parentListTypes: string[]): boolean;
/**
* Checks if there's a list item before the current position
* @param typeOrName - The node type or name
* @param state - The current editor state
* @returns true if a list item exists before current position
*/
function hasListItemBefore(typeOrName: string, state: EditorState): boolean;
/**
* Checks if there's a list item after the current position
* @param typeOrName - The node type or name
* @param state - The current editor state
* @returns true if a list item exists after current position
*/
function hasListItemAfter(typeOrName: string, state: EditorState): boolean;
/**
* Checks if a list item contains a sub-list
* @param name - The item type name
* @param state - The current editor state
* @param node - Optional node to check (defaults to current selection)
* @returns true if the list item has a sub-list
*/
function listItemHasSubList(name: string, state: EditorState, node?: Node): boolean;
/**
* Checks if the next list has a deeper nesting level
* @param typeOrName - The node type or name
* @param state - The current editor state
* @returns true if next list is at a deeper level
*/
function nextListIsDeeper(typeOrName: string, state: EditorState): boolean;
/**
* Checks if the next list has a higher (less nested) level
* @param typeOrName - The node type or name
* @param state - The current editor state
* @returns true if next list is at a higher level
*/
function nextListIsHigher(typeOrName: string, state: EditorState): boolean;
}// Core types from @tiptap/core
import type { Editor } from "@tiptap/core";
// ProseMirror types from @tiptap/pm
import type { EditorState } from "@tiptap/pm/state";
import type { Node, NodeType, ResolvedPos } from "@tiptap/pm/model";
// Extension-specific types
export type ListKeymapOptions = {
/**
* An array of list types. This is used for item and wrapper list matching.
* @default [{ itemName: 'listItem', wrapperNames: ['bulletList', 'orderedList'] }, { itemName: 'taskItem', wrapperNames: ['taskList'] }]
*/
listTypes: Array<{
itemName: string;
wrapperNames: string[];
}>;
};import { ListKeymap } from "@tiptap/extension-list-keymap";
const editor = new Editor({
extensions: [
ListKeymap.configure({
listTypes: [
{
itemName: 'customListItem',
wrapperNames: ['customBulletList', 'customOrderedList'],
},
],
}),
],
});import { listHelpers } from "@tiptap/extension-list-keymap";
import { Editor } from "@tiptap/core";
// Example with editor instance
const editor = new Editor({
extensions: [/* your extensions */],
});
// Check if backspace should be handled specially
const shouldHandle = listHelpers.handleBackspace(editor, 'listItem', ['bulletList', 'orderedList']);
// Find current list item position
const listItemPos = listHelpers.findListItemPos('listItem', editor.state);
if (listItemPos) {
console.log('List item found at depth:', listItemPos.depth);
}
// Check list nesting and navigation
const isDeeper = listHelpers.nextListIsDeeper('listItem', editor.state);
const hasItemBefore = listHelpers.hasListItemBefore('listItem', editor.state);
const hasItemAfter = listHelpers.hasListItemAfter('listItem', editor.state);This package is deprecated. For new projects, use @tiptap/extension-list directly:
import { ListKeymap, listHelpers } from "@tiptap/extension-list";The API remains identical, but the recommended import path is from the main extension package.