diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5d762e4b8a9..9653c66478b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -185,9 +185,6 @@ catalogs:
'@egoist/tailwindcss-icons':
specifier: 1.9.2
version: 1.9.2
- '@emoji-mart/data':
- specifier: 1.2.1
- version: 1.2.1
'@eslint-community/eslint-plugin-eslint-comments':
specifier: 4.8.1
version: 4.8.1
@@ -464,9 +461,6 @@ catalogs:
embla-carousel-react:
specifier: 8.6.0
version: 8.6.0
- emoji-mart:
- specifier: 5.6.0
- version: 5.6.0
es-toolkit:
specifier: 1.52.0
version: 1.52.0
@@ -536,6 +530,9 @@ catalogs:
foxact:
specifier: 0.3.11
version: 0.3.11
+ frimousse:
+ specifier: 0.3.0
+ version: 0.3.0
fuse.js:
specifier: 7.5.0
version: 7.5.0
@@ -1360,9 +1357,6 @@ importers:
'@base-ui/react':
specifier: 'catalog:'
version: 1.8.0(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
- '@emoji-mart/data':
- specifier: 'catalog:'
- version: 1.2.1
'@floating-ui/react':
specifier: 'catalog:'
version: 0.27.20(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
@@ -1486,9 +1480,6 @@ importers:
embla-carousel-react:
specifier: 'catalog:'
version: 8.6.0(react@19.3.0)
- emoji-mart:
- specifier: 'catalog:'
- version: 5.6.0
es-toolkit:
specifier: 'catalog:'
version: 1.52.0
@@ -1498,6 +1489,9 @@ importers:
foxact:
specifier: 'catalog:'
version: 0.3.11(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
+ frimousse:
+ specifier: 'catalog:'
+ version: 0.3.0(@typescript/typescript6@6.0.2)(react@19.3.0)
fuse.js:
specifier: 'catalog:'
version: 7.5.0
@@ -2177,9 +2171,6 @@ packages:
'@emnapi/wasi-threads@1.2.2':
resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==}
- '@emoji-mart/data@1.2.1':
- resolution: {integrity: sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==}
-
'@es-joy/jsdoccomment@0.92.0':
resolution: {integrity: sha512-WivZqV5jPTYDQJgIMp0hTsyQESKYNY6yCJr4b0l84tYtQntkAMkc6zARh2vlRQyeg4aYHPUkCUDpvr2IWAiGpQ==}
engines: {node: ^22.22.2 || >=24.15.0}
@@ -6346,9 +6337,6 @@ packages:
embla-carousel@8.6.0:
resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==}
- emoji-mart@5.6.0:
- resolution: {integrity: sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==}
-
emoji-regex-xs@2.0.1:
resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==}
engines: {node: '>=10.0.0'}
@@ -6810,6 +6798,15 @@ packages:
react-dom:
optional: true
+ frimousse@0.3.0:
+ resolution: {integrity: sha512-kO6LMoKY/cLAYEhXXtqLRaLIE6L/DagpFPrUZaLv3LsUa1/8Iza3HhwZcgN8eZ+weXnhv69eoclNUPohcCa/IQ==}
+ peerDependencies:
+ react: ^18 || ^19
+ typescript: '>=5.1.0'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
@@ -10193,8 +10190,6 @@ snapshots:
tslib: 2.8.1
optional: true
- '@emoji-mart/data@1.2.1': {}
-
'@es-joy/jsdoccomment@0.92.0':
dependencies:
'@types/estree': 1.0.9
@@ -13962,8 +13957,6 @@ snapshots:
embla-carousel@8.6.0: {}
- emoji-mart@5.6.0: {}
-
emoji-regex-xs@2.0.1: {}
empathic@2.0.1: {}
@@ -14584,6 +14577,12 @@ snapshots:
react: 19.3.0
react-dom: 19.3.0(react@19.3.0)
+ frimousse@0.3.0(@typescript/typescript6@6.0.2)(react@19.3.0):
+ dependencies:
+ react: 19.3.0
+ optionalDependencies:
+ typescript: '@typescript/typescript6@6.0.2'
+
fs-constants@1.0.0:
optional: true
@@ -18102,7 +18101,6 @@ time:
'@chromatic-com/storybook@5.3.1': '2026-09-02T13:43:27.595Z'
'@cucumber/cucumber@13.2.1': '2026-08-04T07:20:28.681Z'
'@egoist/tailwindcss-icons@1.9.2': '2026-01-31T10:48:44.594Z'
- '@emoji-mart/data@1.2.1': '2024-04-25T15:36:14.556Z'
'@eslint-community/eslint-plugin-eslint-comments@4.8.1': '2026-09-12T22:48:47.148Z'
'@eslint-react/eslint-plugin@5.19.1': '2026-09-14T17:56:33.726Z'
'@eslint/markdown@8.0.3': '2026-07-01T13:28:44.339Z'
@@ -18196,7 +18194,6 @@ time:
embla-carousel-autoplay@8.6.0: '2025-04-04T17:37:46.303Z'
embla-carousel-fade@8.6.0: '2025-04-04T17:37:50.278Z'
embla-carousel-react@8.6.0: '2025-04-04T17:37:53.976Z'
- emoji-mart@5.6.0: '2024-04-25T14:22:21.440Z'
es-toolkit@1.52.0: '2026-08-28T07:22:17.687Z'
eslint-config-flat-gitignore@2.4.0: '2026-08-27T07:24:43.063Z'
eslint-markdown@0.17.0: '2026-09-16T06:20:36.564Z'
@@ -18220,6 +18217,7 @@ time:
eventsource-parser@4.1.1: '2026-09-15T18:30:29.195Z'
fast-deep-equal@3.1.3: '2020-06-08T07:27:28.474Z'
foxact@0.3.11: '2026-09-05T12:25:54.102Z'
+ frimousse@0.3.0: '2025-07-15T10:44:32.958Z'
fuse.js@7.5.0: '2026-07-13T17:23:36.705Z'
happy-dom@20.14.5: '2026-09-12T00:07:47.688Z'
hast-util-to-jsx-runtime@2.3.6: '2025-03-05T11:30:29.166Z'
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index e6160a92379..04ff09c4ba0 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -68,7 +68,6 @@ catalog:
'@chromatic-com/storybook': 5.3.1
'@cucumber/cucumber': 13.2.1
'@egoist/tailwindcss-icons': 1.9.2
- '@emoji-mart/data': 1.2.1
'@eslint-community/eslint-plugin-eslint-comments': 4.8.1
'@eslint-react/eslint-plugin': 5.19.1
'@eslint/markdown': 8.0.3
@@ -162,7 +161,6 @@ catalog:
embla-carousel-autoplay: 8.6.0
embla-carousel-fade: 8.6.0
embla-carousel-react: 8.6.0
- emoji-mart: 5.6.0
es-toolkit: 1.52.0
eslint: 10.10.0
eslint-config-flat-gitignore: 2.4.0
@@ -186,6 +184,7 @@ catalog:
eventsource-parser: 4.1.1
fast-deep-equal: 3.1.3
foxact: 0.3.11
+ frimousse: 0.3.0
fuse.js: 7.5.0
happy-dom: 20.14.5
hast-util-to-jsx-runtime: 2.3.6
diff --git a/vite.config.ts b/vite.config.ts
index 8d47f7528bb..dee3d7eccca 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -40,6 +40,8 @@ const generatedIgnores = [
'web/public/embed.min.js',
'web/public/pdf.worker.min.mjs',
'web/public/vs/**',
+ // Vendored Emojibase JSON is served verbatim.
+ 'web/public/emoji/emojibase-*/**',
]
const formatterUnstableInputs = ['web/app/components/develop/template/*.mdx']
diff --git a/web/app/components/app/configuration/dataset-config/card-item/__tests__/index.spec.tsx b/web/app/components/app/configuration/dataset-config/card-item/__tests__/index.spec.tsx
index acb21be3f6a..48424a0322c 100644
--- a/web/app/components/app/configuration/dataset-config/card-item/__tests__/index.spec.tsx
+++ b/web/app/components/app/configuration/dataset-config/card-item/__tests__/index.spec.tsx
@@ -218,7 +218,7 @@ describe('dataset-config/card-item', () => {
const iconElement = nameElement.parentElement?.firstElementChild as HTMLElement
expect(iconElement)!.toHaveStyle({ background: '#FFF4ED' })
- expect(iconElement.querySelector('em-emoji'))!.toHaveAttribute('id', '๐')
+ expect(iconElement).toHaveTextContent('๐')
})
it('should apply mask overlay on mobile when drawer is open', async () => {
diff --git a/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx b/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx
index 4b5ca839428..5169868b53a 100644
--- a/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx
+++ b/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx
@@ -1,4 +1,5 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
+import { mockEmojiData } from '@/test/emoji-picker'
import ExternalDataToolModal from '../external-data-tool-modal'
const mockToastError = vi.fn()
@@ -131,9 +132,9 @@ describe('ExternalDataToolModal', () => {
fireEvent.click(screen.getByText('pick-extension'))
fireEvent.click(screen.getByText('open-emoji-picker'))
await waitFor(() => {
- expect(screen.getByPlaceholderText('Search emojis...')).toBeInTheDocument()
+ expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument()
})
- const emojiButton = document.querySelector('em-emoji')?.closest('button')
+ const emojiButton = await screen.findByRole('gridcell', { name: 'Grinning face' })
expect(emojiButton).toBeTruthy()
fireEvent.click(emojiButton!)
fireEvent.click(screen.getByRole('button', { name: '#E4FBCC' }))
@@ -228,3 +229,5 @@ describe('ExternalDataToolModal', () => {
expect(mockOnCancel).toHaveBeenCalled()
})
})
+
+mockEmojiData()
diff --git a/web/app/components/app/create-app-modal/__tests__/index.spec.tsx b/web/app/components/app/create-app-modal/__tests__/index.spec.tsx
index 3bbe0d7c8a1..68af8670fe4 100644
--- a/web/app/components/app/create-app-modal/__tests__/index.spec.tsx
+++ b/web/app/components/app/create-app-modal/__tests__/index.spec.tsx
@@ -4,6 +4,7 @@ import { fireEvent, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
import { useRouter } from '@/next/navigation'
import { renderWithConsoleQuery } from '@/test/console/query-data'
+import { mockEmojiData } from '@/test/emoji-picker'
import { AppModeEnum } from '@/types/app'
import { getRedirection } from '@/utils/app-redirection'
import { trackCreateApp } from '@/utils/create-app-tracking'
@@ -265,12 +266,12 @@ describe('CreateAppModal', () => {
fireEvent.click(screen.getByText('app.types.chatbot'))
fireEvent.click(screen.getByText('open-icon-picker'))
await waitFor(() => {
- expect(screen.getByPlaceholderText('Search emojis...')).toBeInTheDocument()
+ expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument()
})
fireEvent.click(screen.getByRole('button', { name: '#E4FBCC' }))
fireEvent.click(screen.getByRole('button', { name: /iconPicker\.ok/ }))
await waitFor(() => {
- expect(screen.queryByPlaceholderText('Search emojis...')).not.toBeInTheDocument()
+ expect(screen.queryByPlaceholderText('common.operation.search')).not.toBeInTheDocument()
})
fireEvent.change(screen.getByPlaceholderText('app.newApp.appNamePlaceholder'), {
target: { value: 'Keyboard App' },
@@ -309,14 +310,14 @@ describe('CreateAppModal', () => {
fireEvent.click(screen.getByText('open-icon-picker'))
await waitFor(() => {
- expect(screen.getByPlaceholderText('Search emojis...')).toBeInTheDocument()
+ expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument()
})
fireEvent.click(screen.getByRole('button', { name: /iconPicker\.cancel/ }))
await waitFor(() => {
- expect(screen.queryByPlaceholderText('Search emojis...')).not.toBeInTheDocument()
+ expect(screen.queryByPlaceholderText('common.operation.search')).not.toBeInTheDocument()
})
- expect(screen.queryByPlaceholderText('Search emojis...')).not.toBeInTheDocument()
+ expect(screen.queryByPlaceholderText('common.operation.search')).not.toBeInTheDocument()
ahooksMocks.keyPressHandlers.at(-1)?.()
@@ -379,3 +380,5 @@ describe('CreateAppModal', () => {
})
})
})
+
+mockEmojiData()
diff --git a/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx b/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx
index 1f5adc98622..f36ee285ac1 100644
--- a/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx
+++ b/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx
@@ -41,7 +41,7 @@ vi.mock('@/app/components/base/app-icon-picker', () => ({