@devalok/shilp-sutra 0.47.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +36 -12
- package/BREAKING.json +46 -0
- package/MIGRATION.md +35 -0
- package/dist/_chunks/emoji-suggestion.js +124 -0
- package/dist/_chunks/emoji-suggestion.js.map +1 -0
- package/dist/_chunks/emoji.js +1115 -0
- package/dist/_chunks/emoji.js.map +1 -0
- package/dist/_chunks/tiptap.js.map +1 -1
- package/dist/composed/emoji-picker.d.ts +19 -9
- package/dist/composed/emoji-picker.d.ts.map +1 -1
- package/dist/composed/emoji-picker.js +87 -68
- package/dist/composed/emoji-picker.js.map +1 -1
- package/dist/composed/extensions/emoji-data.d.ts +1 -14
- package/dist/composed/extensions/emoji-data.d.ts.map +1 -1
- package/dist/composed/extensions/emoji-node.d.ts +0 -3
- package/dist/composed/extensions/emoji-node.d.ts.map +1 -1
- package/dist/composed/extensions/emoji-node.js +9 -34
- package/dist/composed/extensions/emoji-node.js.map +1 -1
- package/dist/composed/extensions/emoji-suggestion.d.ts +2 -2
- package/dist/composed/extensions/emoji-suggestion.d.ts.map +1 -1
- package/dist/composed/extensions/emoji-suggestion.js +2 -116
- package/dist/composed/rich-chat-input.d.ts.map +1 -1
- package/dist/composed/rich-chat-input.js +554 -575
- package/dist/composed/rich-chat-input.js.map +1 -1
- package/dist/composed/rich-text-editor.d.ts.map +1 -1
- package/dist/composed/rich-text-editor.js +167 -190
- package/dist/composed/rich-text-editor.js.map +1 -1
- package/docs/components/composed/emoji-picker.md +19 -17
- package/docs/components/composed/rich-text-editor.md +1 -1
- package/docs/components/ui/table-row-link.md +1 -1
- package/docs/recipes/index.md +1 -1
- package/docs/recipes/install-astro.md +15 -12
- package/docs/recipes/install-next-app-router.md +20 -12
- package/docs/recipes/install-next-pages.md +3 -1
- package/docs/recipes/install-remix.md +16 -13
- package/docs/recipes/install-tanstack-start.md +19 -12
- package/docs/recipes/install-vite.md +18 -15
- package/docs/recipes/troubleshoot.md +7 -8
- package/llms.txt +2 -2
- package/mcp-manifest.json +29 -66
- package/package.json +6 -9
- package/skill/SKILL.md +1 -1
- package/skill/references/components.md +2 -2
- package/skill/references/setup-astro.md +15 -12
- package/skill/references/setup-next-app-router.md +20 -12
- package/skill/references/setup-next-pages.md +3 -1
- package/skill/references/setup-remix.md +16 -13
- package/skill/references/setup-tanstack-start.md +19 -12
- package/skill/references/setup-vite.md +18 -15
- package/skill/references/troubleshoot.md +7 -8
- package/dist/_chunks/emoji-data.js +0 -45
- package/dist/_chunks/emoji-data.js.map +0 -1
- package/dist/composed/extensions/emoji-suggestion.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -20,24 +20,13 @@ If you are a human, read [README.md](./README.md) instead.
|
|
|
20
20
|
|
|
21
21
|
## How to get details, in priority order
|
|
22
22
|
|
|
23
|
-
1. **shilp-sutra MCP** — version-exact answers as JSON, cheapest on context. Hosted at `https://shilp-sutra.devalok.in/mcp` (connect it if it isn't already; it beats reading the frozen docs in `node_modules`).
|
|
23
|
+
1. **shilp-sutra MCP** — version-exact answers as JSON, cheapest on context. Hosted at `https://shilp-sutra.devalok.in/mcp` (connect it if it isn't already; it beats reading the frozen docs in `node_modules`). The tool surface is listed under "MCP tools" below. Always pass the consumer's installed version (from `node_modules/@devalok/shilp-sutra/package.json`) as `version`.
|
|
24
24
|
2. **`packages/core/llms.txt`** — the ~3K-token router: what exists + where to get detail. Load this by default; it is deliberately tiny.
|
|
25
25
|
3. **`packages/core/docs/components/<tier>/<name>.md`** — single-component doc (~3K tokens: props, examples, composability, gotchas). Read ONLY the components you're using — never bulk-read the directory or concatenate these.
|
|
26
26
|
4. **`packages/core/mcp-manifest.json`** — everything machine-readable (props, tokens, composition; react-docgen shape). Prefer targeted reads over prose when you need structured data without the MCP.
|
|
27
27
|
5. **`packages/core/docs/recipes/<framework>.md`** — copy-paste install + setup for the user's framework.
|
|
28
28
|
6. **`packages/core/docs/recipes/upgrading.md`** + **`MIGRATION.md`** — read BOTH before any version bump. See the hard constraint below.
|
|
29
29
|
|
|
30
|
-
### Setting up in a new project (do this in order)
|
|
31
|
-
|
|
32
|
-
If the MCP is connected, run the setup sequence instead of guessing — it closes the four traps that break agent-driven installs (peer-dep cliffs, silent TW4 dead classes, wrong recipe, mis-wired CSS/config):
|
|
33
|
-
|
|
34
|
-
1. `detect_framework(packageJson)` → the right recipe id + package manager (don't assume App Router, or that `create-remix` still makes Remix).
|
|
35
|
-
2. `get_setup(recipe)` → the full, version-live recipe. Follow every step.
|
|
36
|
-
3. `preflight(framework, imports)` → the exact install for the OPTIONAL peer deps your imports need. Run BEFORE first import or the build fails with `Failed to resolve import`.
|
|
37
|
-
4. `validate_snippet(code)` → run on each file BEFORE writing it. Catches bare `shadow`, `-surface-N`, `bg-gradient-to-*`, removed Button variants, invalid enum props — the dead classes fail silently (no error, no style).
|
|
38
|
-
5. `verify_setup(globalsCss, nextConfig, imports, installedDeps)` → confirm CSS imports + order, `transpilePackages`, and peer coverage after wiring.
|
|
39
|
-
|
|
40
|
-
Without the MCP, the same information is in `docs/recipes/` (per-framework §2a lists optional peers) and the `@devalok/eslint-plugin-shilp-sutra` plugin catches the dead classes at lint time.
|
|
41
30
|
7. **`node_modules/@devalok/shilp-sutra/BREAKING.json`** — machine-readable manifest of every breaking change per version (moves, type narrowings, removals, renames). Read this programmatically when planning an upgrade — schema in `BREAKING.schema.json`. Lets you answer "does my code import any of these moved symbols?" without parsing CHANGELOG prose.
|
|
42
31
|
|
|
43
32
|
(`llms-full.txt` and `llms-quick.txt` were removed in 0.45 — the router + per-component docs + manifest replaced them. Do not look for them.)
|
|
@@ -51,6 +40,41 @@ When the package is installed in a consumer project, the same files live at:
|
|
|
51
40
|
|
|
52
41
|
Read these local files. Your training data is outdated and will hallucinate APIs that do not exist in the installed version.
|
|
53
42
|
|
|
43
|
+
### MCP tools
|
|
44
|
+
|
|
45
|
+
<!-- BEGIN:mcp-tools --><!-- generated by scripts/generate-tool-list.mjs — do not edit by hand -->
|
|
46
|
+
_11 tools (source: `packages/mcp-server/src/index.mjs`):_
|
|
47
|
+
|
|
48
|
+
**Reference:**
|
|
49
|
+
- `find_component` — Search shilp-sutra components by keyword (authoritative component index).
|
|
50
|
+
- `get_component` — Authoritative, version-exact reference for one component: props/variants/defaults as JSON, usage rules, examples, composition (compound parts,…
|
|
51
|
+
- `get_setup` — Framework install recipe (vite, next-app-router, next-pages, remix, astro, tanstack-start) or guides (customize-brand, server-components, troubleshoot,…
|
|
52
|
+
- `get_tokens` — Design-token reference (color, spacing, typography, radius, shadow, motion, z) as JSON.
|
|
53
|
+
- `search_docs` — Full-text search across component docs and recipes for patterns and guidance the other tools don't slice (e.g. "focus ring", "dark mode toggle").
|
|
54
|
+
- `upgrade` — Structured breaking changes + migration pointers between two shilp-sutra versions.
|
|
55
|
+
|
|
56
|
+
**Setup:**
|
|
57
|
+
- `detect_framework` — Given the consumer app's package.json, returns the correct setup recipe id + package manager, so you never guess (or follow a recipe for the wrong framework).
|
|
58
|
+
- `preflight` — Before writing setup code: given a framework and the component subpaths you intend to import, returns the exact install command for the optional PEER…
|
|
59
|
+
- `validate_snippet` — Pre-write linter.
|
|
60
|
+
- `verify_setup` — Post-install gate.
|
|
61
|
+
|
|
62
|
+
**Write:**
|
|
63
|
+
- `report_issue` — File a bug report, feature request, suggestion, or docs-gap on the shilp-sutra repo when you hit a wall using the design system.
|
|
64
|
+
<!-- END:mcp-tools -->
|
|
65
|
+
|
|
66
|
+
### Setting up in a new project (do this in order)
|
|
67
|
+
|
|
68
|
+
If the MCP is connected, run the setup sequence instead of guessing — it closes the four traps that break agent-driven installs (peer-dep cliffs, silent TW4 dead classes, wrong recipe, mis-wired CSS/config):
|
|
69
|
+
|
|
70
|
+
1. `detect_framework(packageJson)` → the right recipe id + package manager (don't assume App Router, or that `create-remix` still makes Remix).
|
|
71
|
+
2. `get_setup(recipe)` → the full, version-live recipe. Follow every step.
|
|
72
|
+
3. `preflight(framework, imports)` → the exact install for the OPTIONAL peer deps your imports need. Run BEFORE first import or the build fails with `Failed to resolve import`.
|
|
73
|
+
4. `validate_snippet(code)` → run on each file BEFORE writing it. Catches bare `shadow`, `-surface-N`, `bg-gradient-to-*`, removed Button variants, invalid enum props — the dead classes fail silently (no error, no style).
|
|
74
|
+
5. `verify_setup(globalsCss, nextConfig, imports, installedDeps)` → confirm CSS imports + order, `transpilePackages`, and peer coverage after wiring.
|
|
75
|
+
|
|
76
|
+
Without the MCP, the same information is in `docs/recipes/` (per-framework §2a lists optional peers) and the `@devalok/eslint-plugin-shilp-sutra` plugin catches the dead classes at lint time.
|
|
77
|
+
|
|
54
78
|
## Setup playbook (when adding shilp-sutra to a new project)
|
|
55
79
|
|
|
56
80
|
If a user asks you to add shilp-sutra and the package is not yet installed:
|
package/BREAKING.json
CHANGED
|
@@ -243,6 +243,52 @@
|
|
|
243
243
|
"New hosted MCP at https://shilp-sutra.devalok.in/mcp — six read-only tools, version param on every tool; upgrade(from,to) accepts pre-0.45 versions.",
|
|
244
244
|
"New mcp-manifest.json ships at the package root (schema: mcp-manifest.schema.json)."
|
|
245
245
|
]
|
|
246
|
+
},
|
|
247
|
+
"0.48.0": {
|
|
248
|
+
"summary": "Emoji picker migrated from @emoji-mart/react to frimousse — native-only. Non-native art-style sets removed; a few emoji-extension exports removed/narrowed. Also: optional-peer accuracy fixes (recipes/manifest) and recipe corrections. Non-emoji component APIs unchanged.",
|
|
249
|
+
"migrationDoc": "MIGRATION.md#v0480--emoji-picker-migrated-to-frimousse-native-only",
|
|
250
|
+
"removed": [
|
|
251
|
+
{
|
|
252
|
+
"symbol": "emojiDataLoaders",
|
|
253
|
+
"where": "@devalok/shilp-sutra/composed/emoji-picker",
|
|
254
|
+
"replacement": "None — the emoji dataset is bundled and frimousse fetches its own. Delete usages."
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
"narrowed": [
|
|
258
|
+
{
|
|
259
|
+
"prop": "EmojiNodeAttrs",
|
|
260
|
+
"components": [
|
|
261
|
+
"@devalok/shilp-sutra/composed/extensions/emoji-node"
|
|
262
|
+
],
|
|
263
|
+
"from": "{ id, native, set, x, y }",
|
|
264
|
+
"to": "{ id, native }",
|
|
265
|
+
"fix": "Drop set/x/y — the node renders the native character."
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"prop": "EmojiSuggestionItem",
|
|
269
|
+
"components": [
|
|
270
|
+
"@devalok/shilp-sutra/composed/extensions/emoji-suggestion"
|
|
271
|
+
],
|
|
272
|
+
"from": "{ id, name, native, x, y }",
|
|
273
|
+
"to": "{ id, name, native }",
|
|
274
|
+
"fix": "Drop x/y — native-only, no spritesheet coordinates."
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"prop": "createEmojiSuggestion(set) argument",
|
|
278
|
+
"components": [
|
|
279
|
+
"@devalok/shilp-sutra/composed/extensions/emoji-suggestion"
|
|
280
|
+
],
|
|
281
|
+
"from": "createEmojiSuggestion(set?: string)",
|
|
282
|
+
"to": "createEmojiSuggestion()",
|
|
283
|
+
"fix": "Call with no argument."
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
"notes": [
|
|
287
|
+
"Art-style emoji sets (apple/google/twitter/facebook) removed. set / theme / previewPosition / skinTonePosition (EmojiPicker) and emojiSet (RichChatInput/RichTextEditor) are now accepted-but-ignored no-ops; EmojiSet type retained for source compat. Everyone sees native platform emoji.",
|
|
288
|
+
"frimousse + @emoji-mart/data are now BUNDLED (lazy emoji chunk) — the emoji feature needs ZERO consumer peer installs and no --legacy-peer-deps on React 19 (the old @emoji-mart/react peer had no React-19 support).",
|
|
289
|
+
"New emojibaseUrl prop on EmojiPicker/EmojiPickerPopover to self-host the emoji dataset (removes the runtime jsdelivr CDN dependency for strict-CSP / offline apps).",
|
|
290
|
+
"Optional-peer accuracy: recipe §2a tables are now generated from source; added sonner / remark-gfm / date-fns / @tanstack/react-table (data-table-toolbar) rows and removed phantom @tiptap install instructions."
|
|
291
|
+
]
|
|
246
292
|
}
|
|
247
293
|
}
|
|
248
294
|
}
|
package/MIGRATION.md
CHANGED
|
@@ -4,6 +4,41 @@ This page indexes all breaking changes across `@devalok/shilp-sutra` versions. F
|
|
|
4
4
|
|
|
5
5
|
> **Upgrading from < 0.36?** Start here, then read each intermediate version section. Breaking changes stack — skipping versions means stacking migrations.
|
|
6
6
|
|
|
7
|
+
## v0.48.0 — Emoji picker migrated to frimousse (native-only)
|
|
8
|
+
|
|
9
|
+
The emoji picker moved from `@emoji-mart/react` to **frimousse**. `@emoji-mart/react` never declared React 19 support (`peer react "^16.8 || ^17 || ^18"`), so React-19 consumers using `EmojiPicker`, `RichChatInput`, or `RichTextEditor` hit a hard `ERESOLVE` on install. frimousse is React 18/19 native. **Non-emoji component APIs are unchanged.**
|
|
10
|
+
|
|
11
|
+
### What changed for consumers
|
|
12
|
+
|
|
13
|
+
- **Native emoji only.** The art-style sets (`apple`/`google`/`twitter`/`facebook`) are gone — everyone sees their own platform's native emoji glyphs.
|
|
14
|
+
- **Zero emoji peers.** frimousse and the `:shortcode:` dataset (`@emoji-mart/data`, pure JSON) are now **bundled** into a lazy chunk. You no longer install anything for emoji, and no more `--legacy-peer-deps` on React 19.
|
|
15
|
+
- **New built-in footer** (hovered-emoji preview + skin-tone selector) and a new `emojibaseUrl` prop to self-host the dataset (strict-CSP / offline).
|
|
16
|
+
|
|
17
|
+
### Migrate
|
|
18
|
+
|
|
19
|
+
Your build won't break on the prop changes (the old props are accepted-but-ignored), but clean them up:
|
|
20
|
+
|
|
21
|
+
```diff
|
|
22
|
+
- <EmojiPicker set="apple" theme="dark" previewPosition="none" skinTonePosition="search" onSelect={…} />
|
|
23
|
+
+ <EmojiPicker onSelect={…} /> // native only; theme follows the .dark class
|
|
24
|
+
|
|
25
|
+
- <RichTextEditor emojiSet="google" … />
|
|
26
|
+
+ <RichTextEditor … /> // emojiSet is a no-op now
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Hard breaks (only if you used the emoji extension internals directly):
|
|
30
|
+
|
|
31
|
+
- **`emojiDataLoaders` export removed** (`composed/emoji-picker`) — delete usages; the dataset is bundled.
|
|
32
|
+
- **`EmojiNodeAttrs` narrowed** `{ id, native, set, x, y }` → `{ id, native }`.
|
|
33
|
+
- **`EmojiSuggestionItem` narrowed** — dropped `x` / `y`.
|
|
34
|
+
- **`createEmojiSuggestion(set?)` → `createEmojiSuggestion()`** — call with no argument.
|
|
35
|
+
|
|
36
|
+
If you were on React 19 forcing `--legacy-peer-deps` for emoji, you can drop that workaround.
|
|
37
|
+
|
|
38
|
+
### Also in this release (non-breaking)
|
|
39
|
+
|
|
40
|
+
- Optional-peer install docs are now generated from source: added the missing `sonner` / `remark-gfm` / `date-fns` / `@tanstack/react-table` rows, removed phantom `@tiptap` / `@emoji-mart` install instructions. If your setup notes told you to `pnpm add @tiptap/*` for the rich-text editor, you can remove that — it's bundled.
|
|
41
|
+
|
|
7
42
|
## v0.47.0 — MCP setup-journey tools + manifest `peers` (additive)
|
|
8
43
|
|
|
9
44
|
**Nothing breaks. No migration required.** Everything in 0.47.0 is additive.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn as e } from "../ui/lib/utils.js";
|
|
3
|
+
import { g as t, p as n, y as r } from "./tiptap.js";
|
|
4
|
+
import * as i from "react";
|
|
5
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
|
+
import { createRoot as s } from "react-dom/client";
|
|
7
|
+
//#region src/composed/extensions/emoji-data.ts
|
|
8
|
+
var c = null;
|
|
9
|
+
async function l() {
|
|
10
|
+
if (c) return c;
|
|
11
|
+
let e = await import("./emoji.js").then((e) => e.t);
|
|
12
|
+
return c = e.default ?? e, c;
|
|
13
|
+
}
|
|
14
|
+
function u(e) {
|
|
15
|
+
return {
|
|
16
|
+
id: e.id,
|
|
17
|
+
name: e.name,
|
|
18
|
+
native: e.skins[0]?.native ?? ""
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function d(e, t, n = 8) {
|
|
22
|
+
let r = Object.values(e.emojis);
|
|
23
|
+
if (!t) return r.slice(0, n).map(u);
|
|
24
|
+
let i = t.toLowerCase(), a = [];
|
|
25
|
+
for (let e of r) {
|
|
26
|
+
if (a.length >= n) break;
|
|
27
|
+
(e.id.includes(i) || e.name.toLowerCase().includes(i) || e.keywords?.some((e) => e.includes(i))) && a.push(u(e));
|
|
28
|
+
}
|
|
29
|
+
return a;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/composed/extensions/emoji-suggestion.tsx
|
|
33
|
+
function f({ emoji: e, size: t = "1.2em" }) {
|
|
34
|
+
return /* @__PURE__ */ a("span", {
|
|
35
|
+
style: { fontSize: t },
|
|
36
|
+
children: e.native
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
var p = i.forwardRef(({ items: t, command: n }, r) => {
|
|
40
|
+
let [s, c] = i.useState(0);
|
|
41
|
+
return i.useEffect(() => c(0), [t]), i.useImperativeHandle(r, () => ({ onKeyDown: ({ event: e }) => e.key === "ArrowUp" ? (c((e) => (e + t.length - 1) % t.length), !0) : e.key === "ArrowDown" ? (c((e) => (e + 1) % t.length), !0) : e.key === "Enter" ? (t[s] && n(t[s]), !0) : !1 })), t.length ? /* @__PURE__ */ a("div", {
|
|
42
|
+
role: "listbox",
|
|
43
|
+
"aria-label": "Emoji suggestions",
|
|
44
|
+
className: "z-popover max-h-[200px] overflow-x-hidden overflow-y-auto rounded-control bg-surface-overlay shadow-raised-hover",
|
|
45
|
+
children: t.map((t, r) => /* @__PURE__ */ o("button", {
|
|
46
|
+
type: "button",
|
|
47
|
+
role: "option",
|
|
48
|
+
"aria-selected": r === s,
|
|
49
|
+
onClick: () => n(t),
|
|
50
|
+
className: e("flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left text-ds-sm", r === s ? "bg-surface-raised text-surface-fg" : "text-surface-fg-muted hover:bg-surface-raised"),
|
|
51
|
+
children: [/* @__PURE__ */ a(f, {
|
|
52
|
+
emoji: t,
|
|
53
|
+
size: "1.25em"
|
|
54
|
+
}), /* @__PURE__ */ o("span", {
|
|
55
|
+
className: "truncate",
|
|
56
|
+
children: [
|
|
57
|
+
":",
|
|
58
|
+
t.id,
|
|
59
|
+
":"
|
|
60
|
+
]
|
|
61
|
+
})]
|
|
62
|
+
}, t.id))
|
|
63
|
+
}) : null;
|
|
64
|
+
});
|
|
65
|
+
p.displayName = "EmojiList";
|
|
66
|
+
function m() {
|
|
67
|
+
return () => {
|
|
68
|
+
let e = null, t = null, n = null;
|
|
69
|
+
return {
|
|
70
|
+
onStart: (r) => {
|
|
71
|
+
t = document.createElement("div"), t.style.position = "absolute", t.style.zIndex = "1400";
|
|
72
|
+
let i = r.clientRect?.();
|
|
73
|
+
i && (t.style.left = `${i.left}px`, t.style.top = `${i.bottom + 4}px`), document.body.appendChild(t), e = s(t), e.render(/* @__PURE__ */ a(p, {
|
|
74
|
+
ref: (e) => {
|
|
75
|
+
n = e;
|
|
76
|
+
},
|
|
77
|
+
items: r.items,
|
|
78
|
+
command: (e) => r.command(e)
|
|
79
|
+
}));
|
|
80
|
+
},
|
|
81
|
+
onUpdate: (r) => {
|
|
82
|
+
if (!e || !t) return;
|
|
83
|
+
let i = r.clientRect?.();
|
|
84
|
+
i && (t.style.left = `${i.left}px`, t.style.top = `${i.bottom + 4}px`), e.render(/* @__PURE__ */ a(p, {
|
|
85
|
+
ref: (e) => {
|
|
86
|
+
n = e;
|
|
87
|
+
},
|
|
88
|
+
items: r.items,
|
|
89
|
+
command: (e) => r.command(e)
|
|
90
|
+
}));
|
|
91
|
+
},
|
|
92
|
+
onKeyDown: (r) => r.event.key === "Escape" ? (t && (e?.unmount(), t.remove(), t = null, e = null), !0) : n?.onKeyDown(r) ?? !1,
|
|
93
|
+
onExit: () => {
|
|
94
|
+
t && (e?.unmount(), t.remove(), t = null, e = null);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function h() {
|
|
100
|
+
return t.create({
|
|
101
|
+
name: "emojiSuggestion",
|
|
102
|
+
addProseMirrorPlugins() {
|
|
103
|
+
return [n({
|
|
104
|
+
pluginKey: new r("emojiSuggestion"),
|
|
105
|
+
editor: this.editor,
|
|
106
|
+
char: ":",
|
|
107
|
+
items: async ({ query: e }) => d(await l(), e, 8),
|
|
108
|
+
command: ({ editor: e, range: t, props: n }) => {
|
|
109
|
+
let r = n;
|
|
110
|
+
e.chain().focus().deleteRange(t).insertContent({
|
|
111
|
+
type: "emojiNode",
|
|
112
|
+
attrs: {
|
|
113
|
+
id: r.id,
|
|
114
|
+
native: r.native
|
|
115
|
+
}
|
|
116
|
+
}).run();
|
|
117
|
+
},
|
|
118
|
+
render: m()
|
|
119
|
+
})];
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { h as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emoji-suggestion.js","names":[],"sources":["../../src/composed/extensions/emoji-data.ts","../../src/composed/extensions/emoji-suggestion.tsx"],"sourcesContent":["// Emoji dataset for the editors' `:shortcode:` autocomplete. Sources\n// `@emoji-mart/data` — a pure-JSON package with NO react peer, so it is\n// unaffected by the React-19 peer issue that drove the picker off\n// `@emoji-mart/react` (→ frimousse). Native-only since that migration: no\n// spritesheet coordinates, no per-art-style loaders.\n\ninterface EmojiSkin {\n native: string\n}\n\ninterface EmojiEntry {\n id: string\n name: string\n keywords?: string[]\n skins: EmojiSkin[]\n}\n\nexport interface EmojiDataset {\n emojis: Record<string, EmojiEntry>\n}\n\nexport interface ResolvedEmoji {\n id: string\n name: string\n native: string\n}\n\nlet cachedData: EmojiDataset | null = null\n\nexport async function loadEmojiData(): Promise<EmojiDataset> {\n if (cachedData) return cachedData\n const mod = await import('@emoji-mart/data')\n cachedData = ((mod as { default?: EmojiDataset }).default ?? mod) as EmojiDataset\n return cachedData\n}\n\nfunction resolve(entry: EmojiEntry): ResolvedEmoji {\n return { id: entry.id, name: entry.name, native: entry.skins[0]?.native ?? '' }\n}\n\nexport function searchEmoji(data: EmojiDataset, query: string, limit = 8): ResolvedEmoji[] {\n const entries = Object.values(data.emojis)\n if (!query) return entries.slice(0, limit).map(resolve)\n const q = query.toLowerCase()\n const results: ResolvedEmoji[] = []\n for (const e of entries) {\n if (results.length >= limit) break\n if (\n e.id.includes(q) ||\n e.name.toLowerCase().includes(q) ||\n e.keywords?.some((k) => k.includes(q))\n ) {\n results.push(resolve(e))\n }\n }\n return results\n}\n","import { Extension } from '@tiptap/core'\nimport Suggestion, { type SuggestionKeyDownProps,type SuggestionProps } from '@tiptap/suggestion'\nimport { PluginKey } from 'prosemirror-state'\nimport * as React from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\n\nimport { cn } from '../../ui/lib/utils'\nimport { loadEmojiData, type ResolvedEmoji, searchEmoji } from './emoji-data'\n\n// Re-export for external consumers\nexport type { ResolvedEmoji as EmojiSuggestionItem }\n\n// Native-only since the frimousse migration.\nfunction EmojiImage({ emoji, size = '1.2em' }: { emoji: ResolvedEmoji; size?: string }) {\n return <span style={{ fontSize: size }}>{emoji.native}</span>\n}\n\ninterface EmojiListRef {\n onKeyDown: (props: SuggestionKeyDownProps) => boolean\n}\n\ninterface EmojiListProps {\n items: ResolvedEmoji[]\n command: (item: ResolvedEmoji) => void\n}\n\nconst EmojiList = React.forwardRef<EmojiListRef, EmojiListProps>(\n ({ items, command }, ref) => {\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n\n React.useEffect(() => setSelectedIndex(0), [items])\n\n React.useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }: SuggestionKeyDownProps) => {\n if (event.key === 'ArrowUp') {\n setSelectedIndex((i) => (i + items.length - 1) % items.length)\n return true\n }\n if (event.key === 'ArrowDown') {\n setSelectedIndex((i) => (i + 1) % items.length)\n return true\n }\n if (event.key === 'Enter') {\n if (items[selectedIndex]) command(items[selectedIndex])\n return true\n }\n return false\n },\n }))\n\n if (!items.length) return null\n\n return (\n <div role=\"listbox\" aria-label=\"Emoji suggestions\" className=\"z-popover max-h-[200px] overflow-x-hidden overflow-y-auto rounded-control bg-surface-overlay shadow-raised-hover\">\n {items.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n onClick={() => command(item)}\n className={cn(\n 'flex w-full items-center gap-ds-03 px-ds-04 py-ds-02b text-left text-ds-sm',\n index === selectedIndex ? 'bg-surface-raised text-surface-fg' : 'text-surface-fg-muted hover:bg-surface-raised',\n )}\n >\n <EmojiImage emoji={item} size=\"1.25em\" />\n <span className=\"truncate\">:{item.id}:</span>\n </button>\n ))}\n </div>\n )\n },\n)\nEmojiList.displayName = 'EmojiList'\n\nfunction createEmojiSuggestionRenderer() {\n return () => {\n let root: Root | null = null\n let container: HTMLDivElement | null = null\n let componentRef: EmojiListRef | null = null\n\n return {\n onStart: (props: SuggestionProps<ResolvedEmoji>) => {\n container = document.createElement('div')\n container.style.position = 'absolute'\n container.style.zIndex = '1400'\n const rect = props.clientRect?.()\n if (rect) {\n container.style.left = `${rect.left}px`\n container.style.top = `${rect.bottom + 4}px`\n }\n document.body.appendChild(container)\n root = createRoot(container)\n root.render(\n <EmojiList\n ref={(r) => { componentRef = r }}\n items={props.items as ResolvedEmoji[]}\n command={(item) => props.command(item)}\n />,\n )\n },\n\n onUpdate: (props: SuggestionProps<ResolvedEmoji>) => {\n if (!root || !container) return\n const rect = props.clientRect?.()\n if (rect) {\n container.style.left = `${rect.left}px`\n container.style.top = `${rect.bottom + 4}px`\n }\n root.render(\n <EmojiList\n ref={(r) => { componentRef = r }}\n items={props.items as ResolvedEmoji[]}\n command={(item) => props.command(item)}\n />,\n )\n },\n\n onKeyDown: (props: SuggestionKeyDownProps) => {\n if (props.event.key === 'Escape') {\n if (container) {\n root?.unmount()\n container.remove()\n container = null\n root = null\n }\n return true\n }\n return componentRef?.onKeyDown(props) ?? false\n },\n\n onExit: () => {\n if (container) {\n root?.unmount()\n container.remove()\n container = null\n root = null\n }\n },\n }\n }\n}\n\n/** Factory: returns the configured `:shortcode:` emoji suggestion extension. */\nexport function createEmojiSuggestion() {\n return Extension.create({\n name: 'emojiSuggestion',\n\n addProseMirrorPlugins() {\n return [\n Suggestion({\n pluginKey: new PluginKey('emojiSuggestion'),\n editor: this.editor,\n char: ':',\n items: async ({ query }) => {\n const data = await loadEmojiData()\n return searchEmoji(data, query, 8)\n },\n command: ({ editor, range, props: item }) => {\n const emoji = item as ResolvedEmoji\n editor\n .chain()\n .focus()\n .deleteRange(range)\n .insertContent({\n type: 'emojiNode',\n attrs: { id: emoji.id, native: emoji.native },\n })\n .run()\n },\n render: createEmojiSuggestionRenderer(),\n }),\n ]\n },\n })\n}\n"],"mappings":"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"}
|