Files
upstream-affine/blocksuite/docs-site/guide/selection.md
upstream-mirror 702cc698a7
Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Release / Prepare (push) Failing after 1s
Release / Release Mobile (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Cloud (push) Has been skipped
Copilot Test Automatically / Setup Test (push) Failing after 1s
snapshot of https://github.com/toeverything/AFFiNE.git (canary, 2026-09-16; blobs > 131072B omitted)
2026-09-16 04:01:20 +05:00

5.3 KiB

Selection

Selection is a very common concept in structure editors. It's used for representing the current cursor position or the current selected blocks.

In BlockSuite, we use a data driven approach to represent the selection. It also follows the CRDT-native data flow, which means the selection state is always derived from serializable data.

Selection Model

The selection model contains a list of atomic selections. Each selection represents a range of the content. For example, if you have a text block with the following content:

Hello

World

In the default PageEditor, it will be modeled as following block tree nodes:

Root Block
  Note Block
    Paragraph Block 1
    Paragraph Block 2

So if you select the text partially via mouse drag as following:

text-selection-example

The selection model will be:

[
  {
    type: 'text',
    group: 'note',
    from: {
      path: ['root_id', 'note_id', 'paragraph_1_id'],
      index: 1,
      length: 5,
    },
    to: {
      path: ['root_id', 'note_id', 'paragraph_2_id'],
      index: 0,
      length: 4,
    },
  },
];

If you select the blocks via block level selection like this:

block-selection-example

The selection model will be:

[
  {
    type: 'block',
    group: 'note',
    path: ['root_id', 'note_id', 'paragraph_1_id'],
  },
  {
    type: 'block',
    group: 'note',
    path: ['root_id', 'note_id', 'paragraph_2_id'],
  },
];

Types and Groups

Selection model has two important properties: type and group.

The type of a selection means which kind of selection it is. And the group of a selection indicates the scope of selection.

Some types of selections can share the same group because they have the same scope. For example, the text selection and the block selection can share the note group because they are both in the affine:note block. And you may also have a cell and row selection in a table block, and they can share the table group.

Update Selection State

You can get the selection manager from std.selection or host.selection. With the selection manager, you can read the selection model from value. And you can also write the selection model by set and update:

const { selection } = host.std;

const current = selection.value;
const next = transformSelection(current);
selection.set(next);

// This can also be written as:
selection.update(current => transformSelection(current));

The set method will override all current selections.

You can also create a new selection by using selection.create method:

const blockSelection = selection.create('block', { path: [0, 1, 2] });

If you want to pick some selections by type from the current selection model, you can reuse the pick and find methods to help:

const textSelection: Selection = selection.pick('text');
const blockSelections: Selection[] = selection.find('block');

You can also clear all the selections by calling clear. If you just want to clear a certain type of selections, you can pass the type as the first argument of clear method:

// clear all selections
selection.clear();

// clear text selection
selection.clear('text');

And we also provide a setGroup method to override the selections in a specific group. Of course, we also provide a getGroup method.

const noteSelections = selection.getGroup('note');
const nextNoteSelections = yourLogic(noteSelections);
selection.setGroup('note', nextNoteSelections);

Subscribe to Selection Changes

You can subscribe to the selection changes by using changed slot.

selection.slots.changed.on(nextSelection => {
  renderSelectionToUI(nextSelection);
});

You can also subscribe to the remote selection changes by using remoteChanged slot. This is useful when you want to display the selection of other users.

selection.slots.remoteChanged.on(nextSelectionMap => {
  for (const [userId, nextSelection] of nextSelectionMap) {
    renderRemoteSelectionToUI(nextSelection, userId);
  }
});

Create Custom Selection

You can create your own selection type by extending the BaseSelection interface.

import { BaseSelection, PathFinder } from '@blocksuite/block-std';
import z from 'zod';

const MySelectionSchema = z.object({
  path: z.array(z.string()),
});

export class MySelection extends BaseSelection {
  static override type = 'mySelection';
  static override group = 'note';

  override equals(other: BaseSelection): boolean {
    if (other instanceof MySelection) {
      return PathFinder.equals(this.path, other.path);
    }
    return false;
  }

  override toJSON(): Record<string, unknown> {
    return {
      type: this.type,
      path: this.path,
    };
  }

  static override fromJSON(json: Record<string, unknown>): ImageSelection {
    MySelectionSchema.parse(json);
    return new MySelection({
      path: json.path as string[],
    });
  }
}

declare global {
  namespace BlockSuite {
    interface Selection {
      mySelection: typeof MySelection;
    }
  }
}

After that, you need to register the selection to selection manager:

selection.register(MySelection);

Now you can use the MySelection in the selection model.

const mySelection = selection.create('mySelection', {
  path: ['a', 'b', 'c'],
});