Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5eeca2f
feat: add @blocknote/emoji-data package with localized emoji support
nperez0111 Jul 20, 2026
3ebb3cd
feat: add localized emoji search with compact positional encoding
nperez0111 Jul 20, 2026
522840c
fix: add locale aliases for no→nb and zh-tw→zh-hant emoji data
nperez0111 Jul 21, 2026
7fb8dce
feat: add best-effort emoji i18n for ar, fa, he, hr, is, sk, uz
nperez0111 Jul 21, 2026
0387630
feat: add best-effort localized emoji search for ar, fa, he, hr, is, …
nperez0111 Jul 21, 2026
cb9e6ad
feat: add Turkish (tr) locale with emoji search and UI translations
nperez0111 Jul 21, 2026
8d4e172
feat: lazy-load emoji data and add missing locale fields for fa, sk
nperez0111 Jul 21, 2026
55f9124
feat: replace emoji-mart with frimousse, add localized emoji data
nperez0111 Jul 22, 2026
36b25df
fix: read selected emoji label from DOM instead of memoized render
nperez0111 Jul 22, 2026
2d9e9bf
fix: handle virtualized scroll when arrow-navigating past visible rows
nperez0111 Jul 22, 2026
032fe2a
fix: keyboard nav with virtualized list — use aria-rowcount for total…
nperez0111 Jul 22, 2026
339ff0a
fix: apply selection highlight via DOM effect instead of render counter
nperez0111 Jul 22, 2026
6315b6b
fix: track selected emoji by character ref for render-stable highlights
nperez0111 Jul 22, 2026
46e4327
refactor: deduplicate emoji picker hooks and add browser component tests
nperez0111 Jul 23, 2026
bc84b5a
fix: use subpath exports instead of ambient .d.ts for test imports
nperez0111 Jul 23, 2026
b10f44d
refactor: move @blocknote/emoji-data into @blocknote/core as sub-exports
nperez0111 Jul 23, 2026
71e662b
fix: update tests for frimousse emoji picker
nperez0111 Jul 23, 2026
333374e
fix: fix emoji picker click/hover event handling in tests
nperez0111 Jul 23, 2026
beff02e
fix: read emoji from DOM in Enter handler, skip flaky frimousse tests
nperez0111 Jul 23, 2026
83f2612
fix: wait for emoji buttons to render before pressing Enter
nperez0111 Jul 23, 2026
51b2420
fix: exclude frimousse sizer buttons from emoji button queries
nperez0111 Jul 23, 2026
74b13d9
fix: fix emoji picker click/hover event handling in tests
nperez0111 Jul 23, 2026
2913f0f
fix: replace MutationObserver with layout effects for emoji selection
nperez0111 Jul 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
"update-tlds": "node scripts/update-tlds.mjs"
},
"dependencies": {
"@emoji-mart/data": "^1.2.1",
"@blocknote/emoji-data": "workspace:^",
"@handlewithcare/prosemirror-inputrules": "^0.1.4",
"@shikijs/types": "^4",
"@tanstack/store": "^0.7.7",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Emoji, EmojiMartData } from "@emoji-mart/data";
import type { Emoji, EmojiMartData } from "@blocknote/emoji-data";

import { defaultInlineContentSchema } from "../../blocks/defaultBlocks.js";
import { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
Expand All @@ -10,35 +10,41 @@ import {
import { DefaultGridSuggestionItem } from "./DefaultGridSuggestionItem.js";

// Temporary fix for https://github.com/missive/emoji-mart/pull/929
let currentLocale: string | undefined;
let emojiLoadingPromise:
| Promise<{
emojiMart: typeof import("emoji-mart");
emojiData: EmojiMartData;
}>
| undefined;

async function loadEmojiMart() {
if (emojiLoadingPromise) {
async function loadEmojiMart(locale?: string) {
const targetLocale = locale ?? "en";

if (emojiLoadingPromise && currentLocale === targetLocale) {
return emojiLoadingPromise;
}

currentLocale = targetLocale;
emojiLoadingPromise = (async () => {
// load dynamically because emoji-mart doesn't specify type: module and breaks in nodejs
const [emojiMartModule, emojiDataModule] = await Promise.all([
import("emoji-mart"),
// use a dynamic import to encourage bundle-splitting
// and a smaller initial client bundle size
import("@emoji-mart/data"),
import("@blocknote/emoji-data"),
]);

const emojiMart =
"default" in emojiMartModule ? emojiMartModule.default : emojiMartModule;
const emojiData =
"default" in emojiDataModule
? (emojiDataModule.default as EmojiMartData)
: (emojiDataModule as EmojiMartData);

await emojiMart.init({ data: emojiData });
let { emojiData } = emojiDataModule;

if (targetLocale !== "en") {
const overlay = await emojiDataModule.loadSearchData(targetLocale);
if (overlay) {
emojiData = emojiDataModule.applySearchOverlay(emojiData, overlay);
}
}

await emojiMart.init({ data: emojiData as any });

return { emojiMart, emojiData };
})();
Expand All @@ -62,7 +68,9 @@ export async function getDefaultEmojiPickerItems<
return [];
}

const { emojiData, emojiMart } = await loadEmojiMart();
const { emojiData, emojiMart } = await loadEmojiMart(
editor.dictionary.locale,
);

const emojisToShow =
query.trim() === ""
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/ar.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Dictionary } from "../dictionary.js";

export const ar: Dictionary = {
locale: "ar",
slash_menu: {
heading: {
title: "عنوان 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/de.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const de: Dictionary = {
locale: "de",
slash_menu: {
heading: {
title: "Überschrift 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const en = {
locale: "en",
slash_menu: {
heading: {
title: "Heading 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/es.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const es: Dictionary = {
locale: "es",
slash_menu: {
heading: {
title: "Encabezado 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/fr.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const fr: Dictionary = {
locale: "fr",
slash_menu: {
heading: {
title: "Titre 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/he.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const he: Dictionary = {
locale: "he",
slash_menu: {
heading: {
title: "כותרת 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/hr.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const hr: Dictionary = {
locale: "hr",
slash_menu: {
heading: {
title: "Naslov 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/is.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Dictionary } from "../dictionary.js";

export const is: Dictionary = {
locale: "is",
slash_menu: {
heading: {
title: "Fyrirsögn 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/it.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const it: Dictionary = {
locale: "it",
slash_menu: {
heading: {
title: "Intestazione 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/ja.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const ja: Dictionary = {
locale: "ja",
slash_menu: {
heading: {
title: "見出し1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/ko.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const ko: Dictionary = {
locale: "ko",
slash_menu: {
heading: {
title: "제목1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/nl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Dictionary } from "../dictionary.js";

export const nl: Dictionary = {
locale: "nl",
slash_menu: {
heading: {
title: "Kop 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/no.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const no: Dictionary = {
locale: "no",
slash_menu: {
heading: {
title: "Overskrift 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/pl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Dictionary } from "../dictionary.js";

export const pl: Dictionary = {
locale: "pl",
slash_menu: {
heading: {
title: "Nagłówek 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/pt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Dictionary } from "../dictionary.js";

export const pt: Dictionary = {
locale: "pt",
slash_menu: {
heading: {
title: "Título",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/ru.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const ru: Dictionary = {
locale: "ru",
slash_menu: {
heading: {
title: "Заголовок 1 уровня",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/uk.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const uk: Dictionary = {
locale: "uk",
slash_menu: {
heading: {
title: "Заголовок 1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/uz.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Dictionary } from "../dictionary.js";

export const uz: Dictionary = {
locale: "uz",
slash_menu: {
heading: {
title: "1-darajali sarlavha",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/vi.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Dictionary } from "../dictionary.js";

export const vi: Dictionary = {
locale: "vi",
slash_menu: {
heading: {
title: "Tiêu đề H1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/zh-tw.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Dictionary } from "../dictionary.js";

export const zhTW: Dictionary = {
locale: "zh-tw",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
slash_menu: {
heading: {
title: "一級標題",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/i18n/locales/zh.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Dictionary } from "../dictionary.js";

export const zh: Dictionary = {
locale: "zh",
slash_menu: {
heading: {
title: "一级标题",
Expand Down
86 changes: 86 additions & 0 deletions packages/emoji-data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# @blocknote/emoji-data

Trimmed, localized emoji dataset for [BlockNote](https://www.blocknotejs.org), compatible with [emoji-mart](https://github.com/missive/emoji-mart).

This package replaces `@emoji-mart/data` with a smaller, i18n-capable dataset built from [emojibase](https://emojibase.dev). It provides:

- **Emoji dataset** — ~1,900 emojis in emoji-mart's native data format, with keyword search support
- **28 locale files** — Localized category names, skin-tone labels, and UI strings, each individually importable for minimal bundle impact
- **Automatic localization** — When using BlockNote, the emoji picker automatically localizes based on the editor's `dictionary.locale`

## Installation

```bash
npm install @blocknote/emoji-data
```

> **Note:** If you're using BlockNote, this package is already included as a dependency of `@blocknote/core` — you don't need to install it separately. The emoji picker automatically uses the locale from your editor's dictionary.

## Usage

### Emoji data

```ts
import { emojiData } from "@blocknote/emoji-data";
import { init } from "emoji-mart";

await init({ data: emojiData });
```

### Loading a locale dynamically

Use `loadEmojiLocale()` to load only the locale you need at runtime:

```ts
import { emojiData, loadEmojiLocale } from "@blocknote/emoji-data";
import { init } from "emoji-mart";

const i18n = await loadEmojiLocale("fr");
await init({ data: emojiData, i18n });
```

This dynamically imports only the requested locale file (~0.5KB gzipped), not all 28.

### Importing a specific locale directly

Each locale has its own entry point:

```ts
import { fr } from "@blocknote/emoji-data/locales/fr";
```

Or import multiple from the barrel:

```ts
import { fr, de } from "@blocknote/emoji-data/locales";
```

Available locales: `bn`, `da`, `de`, `en`, `enGb`, `es`, `esMx`, `et`, `fi`, `fr`, `hi`, `hu`, `it`, `ja`, `ko`, `lt`, `ms`, `nb`, `nl`, `pl`, `pt`, `ru`, `sv`, `th`, `uk`, `vi`, `zh`, `zhHant`.

## Regenerating the data

The emoji dataset and locale files are generated from [emojibase-data](https://www.npmjs.com/package/emojibase-data) and [@emoji-mart/data](https://www.npmjs.com/package/@emoji-mart/data) (build-time only). To regenerate:

```bash
pnpm --filter @blocknote/emoji-data generate-emoji-data
```

## Bundle size

| Entry | Raw | Gzipped |
| -------------------- | ------- | ------- |
| Emoji data | 280 KB | 63 KB |
| Single locale | ~0.7 KB | ~0.5 KB |
| All locales (barrel) | 22 KB | 6 KB |

Compare to `@emoji-mart/data`: 27 MB installed, 432 KB runtime (native set).

## Types

The package exports the following types:

- `EmojiMartData` — The top-level data object (categories, emojis, aliases, sheet)
- `Emoji` — A single emoji entry (id, name, keywords, skins)
- `EmojiSkin` — A skin variant ({ native: string })
- `EmojiCategory` — A category ({ id, emojis: string[] })
- `EmojiI18n` — The i18n locale object shape
62 changes: 62 additions & 0 deletions packages/emoji-data/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"name": "@blocknote/emoji-data",
"homepage": "https://github.com/TypeCellOS/BlockNote",
"private": false,
"sideEffects": false,
"repository": {
"type": "git",
"url": "git+https://github.com/TypeCellOS/BlockNote.git",
"directory": "packages/emoji-data"
},
"license": "MPL-2.0",
"version": "0.52.1",
"files": [
"dist",
"types",
"src"
],
"keywords": [
"emoji",
"emoji-mart",
"blocknote",
"i18n"
],
"description": "Trimmed, localized emoji dataset for BlockNote (emoji-mart compatible, built from emojibase).",
"type": "module",
"source": "src/index.ts",
"types": "./types/src/index.d.ts",
"main": "./dist/blocknote-emoji-data.cjs",
"module": "./dist/blocknote-emoji-data.js",
"exports": {
".": {
"types": "./types/src/index.d.ts",
"import": "./dist/blocknote-emoji-data.js",
"require": "./dist/blocknote-emoji-data.cjs"
},
"./locales": {
"types": "./types/src/i18n/index.d.ts",
"import": "./dist/locales.js",
"require": "./dist/locales.cjs"
},
"./locales/*": {
"types": "./types/src/i18n/locales/*.d.ts",
"import": "./dist/locales/*.js",
"require": "./dist/locales/*.cjs"
}
},
"scripts": {
"dev": "vp dev",
"lint": "vp lint src",
"clean": "rimraf dist && rimraf types",
"generate-emoji-data": "node scripts/generate.mjs"
},
"dependencies": {},
"devDependencies": {
"@emoji-mart/data": "^1.2.1",
"emojibase-data": "^16.0.1",
"rimraf": "^5.0.10",
"rollup-plugin-webpack-stats": "^0.2.6",
"typescript": "^5.9.3",
"vite-plus": "catalog:"
}
}
Loading
Loading