@tensor_1/react 2.18.0 → 2.19.1

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.
Files changed (54) hide show
  1. package/README.md +102 -1
  2. package/dist/components/Comments.d.ts +18 -2
  3. package/dist/components/Comments.js +37 -12
  4. package/dist/components/Composer.d.ts +5 -1
  5. package/dist/components/EditorIcons.d.ts +39 -0
  6. package/dist/components/EditorIcons.js +51 -0
  7. package/dist/components/EmojiGlyph.d.ts +10 -0
  8. package/dist/components/EmojiGlyph.js +18 -0
  9. package/dist/components/EmojiPicker.d.ts +60 -0
  10. package/dist/components/EmojiPicker.js +277 -0
  11. package/dist/components/Markdown.d.ts +36 -0
  12. package/dist/components/Markdown.js +104 -0
  13. package/dist/components/ReactionBar.d.ts +57 -0
  14. package/dist/components/ReactionBar.js +81 -0
  15. package/dist/components/RelationshipGraph.js +23 -4
  16. package/dist/components/RichTextEditor.d.ts +128 -0
  17. package/dist/components/RichTextEditor.js +954 -0
  18. package/dist/components/SaveStatus.d.ts +32 -0
  19. package/dist/components/SaveStatus.js +21 -0
  20. package/dist/components/Tabs.d.ts +10 -3
  21. package/dist/components/Tabs.js +5 -3
  22. package/dist/emoji-image-WQLh8ZQP.d.cts +128 -0
  23. package/dist/emoji-image.d.ts +25 -0
  24. package/dist/emoji-image.js +80 -0
  25. package/dist/emoji-model.d.ts +66 -0
  26. package/dist/emoji-model.js +108 -0
  27. package/dist/emoji.cjs +871 -0
  28. package/dist/emoji.d.cts +57 -0
  29. package/dist/emoji.d.ts +6 -0
  30. package/dist/emoji.js +18 -0
  31. package/dist/index.cjs +587 -519
  32. package/dist/index.d.cts +64 -65
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +3 -0
  35. package/dist/markdown-model.d.ts +75 -0
  36. package/dist/markdown-model.js +438 -0
  37. package/dist/markdown.cjs +585 -0
  38. package/dist/markdown.d.cts +115 -0
  39. package/dist/markdown.d.ts +4 -0
  40. package/dist/markdown.js +9 -0
  41. package/dist/overlay-position-CEGSz6Z2.d.cts +59 -0
  42. package/dist/rich-text/emoji-items.d.ts +14 -0
  43. package/dist/rich-text/emoji-items.js +7 -0
  44. package/dist/rich-text/pipeline.d.ts +91 -0
  45. package/dist/rich-text/pipeline.js +379 -0
  46. package/dist/rich-text/round-trip.d.ts +24 -0
  47. package/dist/rich-text/round-trip.js +11 -0
  48. package/dist/rich-text.cjs +2578 -0
  49. package/dist/rich-text.d.cts +148 -0
  50. package/dist/rich-text.d.ts +8 -0
  51. package/dist/rich-text.js +10 -0
  52. package/dist/url-policy.d.ts +9 -0
  53. package/dist/url-policy.js +11 -0
  54. package/package.json +82 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@ Thin React wrappers over the UIx v2 CSS component library — every component is
7
7
  ```sh
8
8
  npm i @tensor_1/react @tensor_1/tokens
9
9
  ```
10
- Peer deps: `react` / `react-dom` (`^18` or `^19`). Install `echarts` only when using a chart entry.
10
+ Peer deps: `react` / `react-dom` (`^18` or `^19`). Install `echarts` only when using a chart entry, and the Tiptap/`marked`/`emojibase-data` peers only for `./rich-text` and `./emoji` (see below).
11
11
 
12
12
  ## Use
13
13
 
@@ -45,6 +45,89 @@ workflow surfaces, the main entry exports `Pipeline`/`PipelineStage` (compact or
45
45
  Large fixed-height tables can use `useVirtualRows(rows, { rowHeight })`; attach its `containerRef` to
46
46
  `TableWrap` and render the returned row window between spacer rows.
47
47
 
48
+ ## Rich text, markdown and emoji
49
+
50
+ Three optional entries (RTE-01). None of them is imported by the root entry.
51
+
52
+ | Entry | Exports | Needs |
53
+ |---|---|---|
54
+ | `@tensor_1/react/markdown` | `Markdown`, `parseMarkdown`, `parseInline`, `plainText`, `defaultMarkdownIsSafeUrl` | nothing (server-component safe: no hooks, no `"use client"`) |
55
+ | `@tensor_1/react/rich-text` | `RichTextEditor`, `RichTextEditorFallback`, `roundTripMarkdown`, `DEFAULT_RICH_TEXT_LABELS`, `emojiImageFileName` | the optional peers below |
56
+ | `@tensor_1/react/emoji` | `EmojiPicker`, `ReactionBar`, `loadEmojiData`, `buildEmojiData`, `searchEmoji`, `canRenderEmoji`, `emojiImageFileName`, `normalizeEmojiImageBaseUrl` | `emojibase-data` (dynamic import on first open) |
57
+
58
+ Install the optional peers at exactly the versions `@tensor_1/react` declares (`npm view @tensor_1/react peerDependencies`):
59
+ `@tiptap/core`, `@tiptap/pm`, `@tiptap/react`, `@tiptap/starter-kit`, `@tiptap/markdown`, `@tiptap/extension-list`,
60
+ `@tiptap/extension-table`, `@tiptap/extension-image`, `@tiptap/extension-emoji`, `@tiptap/extensions`,
61
+ `@tiptap/suggestion`, `marked`, `emojibase-data` — all MIT. Styles ship in `@tensor_1/tokens` (`.uix-rich-text`,
62
+ `.uix-prose`, `.uix-emoji-picker`, `.uix-reactions`); the editor injects no `<style>` at runtime.
63
+
64
+ ```tsx
65
+ import { RichTextEditor } from "@tensor_1/react/rich-text";
66
+ import { Markdown } from "@tensor_1/react/markdown";
67
+
68
+ <RichTextEditor value={body} onChange={setBody} aria-labelledby="body-label" features="full" maxLength={20000} />;
69
+ <Markdown resolveImageSrc={(src) => (isOurImage(src) ? withBasePath(src) : null)}>{body}</Markdown>;
70
+ ```
71
+
72
+ **Markdown is the stored format, and the editor never rewrites it silently.**
73
+ `onChange` fires only for user edits, never on mount or when `value` changes from outside. Blocks the user did not
74
+ touch keep their exact source bytes, including their spacing, list markers, table alignment, CRLF line endings,
75
+ `{{variables}}`, umlauts and emoji ZWJ sequences. Only edited blocks are re-serialized, and their text is escaped
76
+ only where a markdown reader would misread it (`Tom & Jerry` and `snake_case` stay as typed). Raw HTML is never
77
+ interpreted; it stays literal text. The one exception is `<br>`, which is read as a line break because GFM table cells have no other way to hold one. Gate your own content with the same pipeline, which runs without a DOM:
78
+
79
+ ```ts
80
+ import { roundTripMarkdown } from "@tensor_1/react/rich-text";
81
+ for (const doc of corpus) expect(await roundTripMarkdown(doc.body)).toBe(doc.body);
82
+ ```
83
+
84
+ Pass the markdown from `onChange` back as `value` in the same update, as React state does. The editor treats any
85
+ other `value` as an outside change and replaces its content.
86
+
87
+ Markdown cannot represent everything a user can type. When the editor re-serializes an edited block:
88
+
89
+ - Leading spaces on a line are dropped.
90
+ - Tabs in list items and table cells, and runs of spaces in table cells, become single spaces.
91
+ - A line break inside a checklist item is saved as a new paragraph of that item.
92
+ - Reference definitions (`[id]: url`) are kept. If their surrounding block is deleted, they move to the end.
93
+
94
+ Editor props: `features` (`full` / `comment` / `template`), `headingLevels` (limits the toolbar and input rules;
95
+ existing headings at other levels keep their level), `onUploadImage` (enables the image button and image paste/drop),
96
+ `isSafeUrl`, `resolveImageSrc` (its return value is used verbatim), `onSubmitShortcut` (Ctrl/Cmd+Enter), `emoji`,
97
+ `emojiImageBaseUrl`, `maxLength` (counter; over the limit sets `aria-invalid`), `placeholder`, `disabled`, `readOnly`,
98
+ `labels` (every string, placeholders `{count}` `{max}`), `emojiPickerLabels`, `emojiLocale`, `id`, `name` (adds a
99
+ hidden input), ARIA props, `variant` (`field` / `composer`, which renders in `Composer` with the toolbar in `ComposerBar`),
100
+ `toolbarEnd`, `minRows`, `className`, `onBlur`. Markdown shortcuts (`## `, `- `, `1. `, `[ ] `, `> `, triple backtick)
101
+ work in every preset; the toolbar is one tab stop with arrow-key navigation and scrolls sideways on narrow screens.
102
+ `RichTextEditorFallback` takes the same props and renders a plain textarea, for loading and error-boundary states.
103
+
104
+ **No network access.** The editor, picker and reaction bar make no requests. `@tiptap/extension-emoji` is configured
105
+ with a list stripped of its CDN `fallbackImage` URLs and GitHub image emoji (`forceFallbackImages: false`), and the
106
+ emoji dataset is a bundled dynamic-import chunk. A jsdom egress test guards this.
107
+
108
+ **Emoji fallback images (`emojiImageBaseUrl`).** Unset by default, which means native emoji only; UIx ships no image
109
+ set. On clients without a colour emoji font (VDI, thin clients), pass a same-origin folder (`"/static/emoji"` or
110
+ `"./emoji"`). Values with a scheme or `//` are ignored, with a development warning. Emoji the device cannot draw then
111
+ render as `<img class="uix-emoji-img" src="{base}/{file}" alt="{emoji}">`. `{file}` is every code point of the emoji
112
+ in lowercase hex, joined with `-`, keeping `fe0f` and `200d`:
113
+
114
+ | Emoji | File |
115
+ |---|---|
116
+ | 👍 | `1f44d.png` |
117
+ | ❤️ | `2764-fe0f.png` |
118
+ | 👩‍💻 | `1f469-200d-1f4bb.png` |
119
+ | 👍🏽 | `1f44d-1f3fd.png` |
120
+ | 🇩🇪 | `1f1e9-1f1ea.png` |
121
+
122
+ Image sets that drop `fe0f` from file names (Twemoji, for example) need copies under the full name.
123
+ `emojiImageFileName(emoji)` generates the mapping.
124
+
125
+ `EmojiPicker` (`onSelect`, `trigger`, `locale: 'en' | 'de'`, `labels`, `quickPicks`, `emojiImageBaseUrl`,
126
+ `loadData`) searches localized names, remembers recent picks in `localStorage` and loads its data on first open.
127
+ `ReactionBar` (`reactions`, `onToggle`, `disabled`, `quickPicks`, `labels`, `pickerLabels`, `locale`,
128
+ `emojiImageBaseUrl`) renders `aria-pressed` toggle chips named after the reactors (`{names} reacted with {emoji}`),
129
+ plus an add-reaction picker.
130
+
48
131
  ## UIX-V3 capability components
49
132
 
50
133
  Phase 46.9 adds eleven controlled, domain-neutral components:
@@ -90,4 +173,22 @@ Rules the component cannot enforce for you:
90
173
  - **Dimming is not a signal.** `dimmedNodeIds` must be accompanied by text that says what is dimmed and why.
91
174
  - **Localise everything.** Every visible and announced string comes from `labels`; the English defaults live in `DEFAULT_RELATIONSHIP_GRAPH_LABELS`.
92
175
 
176
+ Radial mode still shortens long labels to fit its ring, but every node carries its full label in a `<title>`, and the `viewBox` grows to contain every node, including nodes you position yourself. Use `layout="layered"` when the labels matter more than the ring.
177
+
178
+ ### Conversations, save feedback and kept tabs
179
+
180
+ - **`Comment variant="system"`** marks a product event, such as a status change, apart from people. It has no speech bubble and a `systemLabel` byline (default "System").
181
+ - **`Comment replyTo`** quotes the answered message above the body. Its label comes from `replyToLabel` (default "In reply to"). Mentions in a body use the `.uix-mention` class.
182
+ - **`SaveStatus`** shows `idle`, `saving`, `saved` or `failed` for inline edits and autosave. It offers `onRetry` when a save fails, and every string comes from `labels`. The status text sits in a polite live region that stays mounted while idle, so keep one `SaveStatus` mounted and change its `state`. Mounting it only when a save starts means the first announcement is lost.
183
+ - **`TabPanel keepMounted`** keeps an inactive panel in the DOM, `hidden` and out of the tab order, so switching back keeps its state and data. It is off by default.
184
+
185
+ ```tsx
186
+ <Comment variant="system" systemLabel={t('system')} meta={ago}>{t('statusChanged', { to })}</Comment>
187
+ <Comment author={name} meta={ago} replyTo={<a href={`#c-${parent.id}`}>{parent.excerpt}</a>} replyToLabel={t('inReplyTo')}>
188
+ {body}
189
+ </Comment>
190
+ <SaveStatus state={saveState} onRetry={save} labels={{ saving: t('saving'), saved: t('saved'), failed: t('saveFailed'), retry: t('retry') }} />
191
+ <TabPanel value="history" keepMounted>{history}</TabPanel>
192
+ ```
193
+
93
194
  Ships **ESM + CJS + types**, with per-file `"use client"` so it's safe under React Server Components. Part of the **[UIx v2 styleguide](https://github.com/COx-ORG-H/UIx)**.
@@ -16,8 +16,24 @@ interface CommentProps extends HTMLAttributes<HTMLDivElement> {
16
16
  author?: ReactNode;
17
17
  meta?: ReactNode;
18
18
  children?: ReactNode;
19
+ /**
20
+ * `person` (default) is someone's message. `system` is an event the product recorded —
21
+ * a status change, an assignment — drawn as chrome rather than as a speech bubble, and
22
+ * bylined with `systemLabel` instead of a person's name.
23
+ */
24
+ variant?: 'person' | 'system';
25
+ /** Byline for `variant="system"` when no `author` is given. Default "System". */
26
+ systemLabel?: string;
27
+ /** The message this one answers, quoted above the body. Text or a link to it. */
28
+ replyTo?: ReactNode;
29
+ /** Label on the quote. Default "In reply to". */
30
+ replyToLabel?: string;
19
31
  }
20
- /** A single comment over `.uix-comment` (avatar column + body). */
21
- declare function Comment({ avatar, author, meta, children, className, ...props }: CommentProps): react.JSX.Element;
32
+ /**
33
+ * A single comment over `.uix-comment` (avatar column + body). `variant="system"` marks
34
+ * product events apart from people; `replyTo` quotes the answered message. Mentions
35
+ * inside the body use the `.uix-mention` token.
36
+ */
37
+ declare function Comment({ avatar, author, meta, children, className, variant, systemLabel, replyTo, replyToLabel, ...props }: CommentProps): react.JSX.Element;
22
38
 
23
39
  export { Comment, type CommentProps, Comments, type CommentsProps };
@@ -3,18 +3,43 @@ import { cx } from "../cx.js";
3
3
  function Comments({ children, className, ...props }) {
4
4
  return /* @__PURE__ */ jsx("div", { role: "log", className: cx("uix-comments", className), ...props, children });
5
5
  }
6
- function Comment({ avatar, author, meta, children, className, ...props }) {
7
- return /* @__PURE__ */ jsxs("div", { className: cx("uix-comment", className), ...props, children: [
8
- avatar ?? /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }),
9
- /* @__PURE__ */ jsxs("div", { className: "uix-comment__body", children: [
10
- (author != null || meta != null) && /* @__PURE__ */ jsxs("div", { className: "uix-comment__meta", children: [
11
- author != null && /* @__PURE__ */ jsx("span", { className: "uix-comment__author", children: author }),
12
- author != null && meta != null ? " \xB7 " : null,
13
- meta
14
- ] }),
15
- children
16
- ] })
17
- ] });
6
+ function Comment({
7
+ avatar,
8
+ author,
9
+ meta,
10
+ children,
11
+ className,
12
+ variant = "person",
13
+ systemLabel = "System",
14
+ replyTo,
15
+ replyToLabel = "In reply to",
16
+ ...props
17
+ }) {
18
+ const system = variant === "system";
19
+ const byline = author ?? (system ? systemLabel : void 0);
20
+ return /* @__PURE__ */ jsxs(
21
+ "div",
22
+ {
23
+ className: cx("uix-comment", system && "uix-comment--system", className),
24
+ "data-variant": system ? "system" : void 0,
25
+ ...props,
26
+ children: [
27
+ avatar ?? /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }),
28
+ /* @__PURE__ */ jsxs("div", { className: "uix-comment__body", children: [
29
+ (byline != null || meta != null) && /* @__PURE__ */ jsxs("div", { className: "uix-comment__meta", children: [
30
+ byline != null && /* @__PURE__ */ jsx("span", { className: "uix-comment__author", children: byline }),
31
+ byline != null && meta != null ? " \xB7 " : null,
32
+ meta
33
+ ] }),
34
+ replyTo != null && /* @__PURE__ */ jsxs("blockquote", { className: "uix-comment__reply", children: [
35
+ /* @__PURE__ */ jsx("span", { className: "uix-comment__reply-label", children: replyToLabel }),
36
+ /* @__PURE__ */ jsx("span", { className: "uix-comment__reply-body", children: replyTo })
37
+ ] }),
38
+ children
39
+ ] })
40
+ ]
41
+ }
42
+ );
18
43
  }
19
44
  export {
20
45
  Comment,
@@ -22,7 +22,11 @@ declare function Composer({ children, className, label, ...props }: ComposerProp
22
22
  interface ComposerBarProps extends HTMLAttributes<HTMLDivElement> {
23
23
  children?: ReactNode;
24
24
  }
25
- /** Action footer for a `<Composer>` over `.uix-composer__bar` (top-bordered, right-aligned). */
25
+ /**
26
+ * Action footer for a `<Composer>` over `.uix-composer__bar` (top-bordered, right-aligned).
27
+ * The bar wraps, so a start group (e.g. a `.uix-segmented` audience toggle) and the submit
28
+ * button stack in a narrow container. No inline `flexWrap` is needed.
29
+ */
26
30
  declare function ComposerBar({ children, className, ...props }: ComposerBarProps): react.JSX.Element;
27
31
 
28
32
  export { Composer, ComposerBar, type ComposerBarProps, type ComposerProps };
@@ -0,0 +1,39 @@
1
+ import * as react from 'react';
2
+ import { SVGProps } from 'react';
3
+
4
+ declare const PATHS: {
5
+ readonly heading1: "M4 12h8M4 18V6M12 18V6M17 12l3-2v8";
6
+ readonly heading2: "M4 12h8M4 18V6M12 18V6M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1";
7
+ readonly heading3: "M4 12h8M4 18V6M12 18V6M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 0 1-2 2M17 17.5c2 1.5 4 .3 4-1.5a2 2 0 0 0-2-2";
8
+ readonly bold: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8";
9
+ readonly italic: "M19 4h-9M14 20H5M15 4 9 20";
10
+ readonly strike: "M16 4H9a3 3 0 0 0-2.83 4M14 12a4 4 0 0 1 0 8H6M4 12h16";
11
+ readonly code: "m16 18 6-6-6-6M8 6l-6 6 6 6";
12
+ readonly bulletList: "M3 12h.01M3 18h.01M3 6h.01M8 12h13M8 18h13M8 6h13";
13
+ readonly orderedList: "M10 12h11M10 18h11M10 6h11M4 10h2M4 6h1v4M6 18H4c0-1 2-2 2-3s-1-1.5-2-1";
14
+ readonly taskList: "m3 17 2 2 4-4M3 7l2 2 4-4M13 6h8M13 12h8M13 18h8";
15
+ readonly blockquote: "M17 6H3M21 12H8M21 18H8M3 12v6";
16
+ readonly codeBlock: "m10 9-3 3 3 3M14 15l3-3-3-3M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z";
17
+ readonly horizontalRule: "M5 12h14";
18
+ readonly link: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71";
19
+ readonly table: "M12 3v18M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2ZM3 9h18M3 15h18";
20
+ readonly tableAddRow: "M4 3h16a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM12 15v6M9 18h6";
21
+ readonly tableAddColumn: "M4 3h6a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM18 9v6M15 12h6";
22
+ readonly tableDeleteRow: "M4 3h16a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM9 18h6";
23
+ readonly tableDeleteColumn: "M4 3h6a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM15 12h6";
24
+ readonly trash: "M3 6h18M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2";
25
+ readonly image: "M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2ZM9 7a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM21 15l-3.09-3.09a2 2 0 0 0-2.82 0L6 21";
26
+ readonly emoji: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20ZM8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01";
27
+ readonly addReaction: "M22 11v1a10 10 0 1 1-9-10M8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01M16 5h6M19 2v6";
28
+ readonly undo: "M9 14 4 9l5-5M4 9h10.5a5.5 5.5 0 0 1 0 11H11";
29
+ readonly redo: "m15 14 5-5-5-5M20 9H9.5a5.5 5.5 0 0 0 0 11H13";
30
+ readonly sourceMode: "M10 12.5 8 15l2 2.5M14 12.5l2 2.5-2 2.5M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z";
31
+ readonly richMode: "M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7ZM8 13h8M8 17h5";
32
+ readonly clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20ZM12 6v6l4 2";
33
+ };
34
+ type EditorIconName = keyof typeof PATHS;
35
+ declare function EditorIcon({ name, ...props }: {
36
+ name: EditorIconName;
37
+ } & SVGProps<SVGSVGElement>): react.JSX.Element;
38
+
39
+ export { EditorIcon, type EditorIconName };
@@ -0,0 +1,51 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ const PATHS = {
3
+ heading1: "M4 12h8M4 18V6M12 18V6M17 12l3-2v8",
4
+ heading2: "M4 12h8M4 18V6M12 18V6M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1",
5
+ heading3: "M4 12h8M4 18V6M12 18V6M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 0 1-2 2M17 17.5c2 1.5 4 .3 4-1.5a2 2 0 0 0-2-2",
6
+ bold: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8",
7
+ italic: "M19 4h-9M14 20H5M15 4 9 20",
8
+ strike: "M16 4H9a3 3 0 0 0-2.83 4M14 12a4 4 0 0 1 0 8H6M4 12h16",
9
+ code: "m16 18 6-6-6-6M8 6l-6 6 6 6",
10
+ bulletList: "M3 12h.01M3 18h.01M3 6h.01M8 12h13M8 18h13M8 6h13",
11
+ orderedList: "M10 12h11M10 18h11M10 6h11M4 10h2M4 6h1v4M6 18H4c0-1 2-2 2-3s-1-1.5-2-1",
12
+ taskList: "m3 17 2 2 4-4M3 7l2 2 4-4M13 6h8M13 12h8M13 18h8",
13
+ blockquote: "M17 6H3M21 12H8M21 18H8M3 12v6",
14
+ codeBlock: "m10 9-3 3 3 3M14 15l3-3-3-3M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z",
15
+ horizontalRule: "M5 12h14",
16
+ link: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71",
17
+ table: "M12 3v18M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2ZM3 9h18M3 15h18",
18
+ tableAddRow: "M4 3h16a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM12 15v6M9 18h6",
19
+ tableAddColumn: "M4 3h6a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM18 9v6M15 12h6",
20
+ tableDeleteRow: "M4 3h16a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM9 18h6",
21
+ tableDeleteColumn: "M4 3h6a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1ZM15 12h6",
22
+ trash: "M3 6h18M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",
23
+ image: "M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2ZM9 7a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM21 15l-3.09-3.09a2 2 0 0 0-2.82 0L6 21",
24
+ emoji: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20ZM8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01",
25
+ addReaction: "M22 11v1a10 10 0 1 1-9-10M8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01M16 5h6M19 2v6",
26
+ undo: "M9 14 4 9l5-5M4 9h10.5a5.5 5.5 0 0 1 0 11H11",
27
+ redo: "m15 14 5-5-5-5M20 9H9.5a5.5 5.5 0 0 0 0 11H13",
28
+ sourceMode: "M10 12.5 8 15l2 2.5M14 12.5l2 2.5-2 2.5M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",
29
+ richMode: "M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7ZM8 13h8M8 17h5",
30
+ clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20ZM12 6v6l4 2"
31
+ };
32
+ function EditorIcon({ name, ...props }) {
33
+ return /* @__PURE__ */ jsx(
34
+ "svg",
35
+ {
36
+ viewBox: "0 0 24 24",
37
+ fill: "none",
38
+ stroke: "currentColor",
39
+ strokeWidth: 2,
40
+ strokeLinecap: "round",
41
+ strokeLinejoin: "round",
42
+ "aria-hidden": "true",
43
+ focusable: "false",
44
+ ...props,
45
+ children: /* @__PURE__ */ jsx("path", { d: PATHS[name] })
46
+ }
47
+ );
48
+ }
49
+ export {
50
+ EditorIcon
51
+ };
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /**
4
+ * Returns a renderer for emoji glyphs. Without a (valid, same-origin) `emojiImageBaseUrl`
5
+ * it returns the emoji text unchanged. With one, emoji this device cannot draw become a
6
+ * same-origin `<img class="uix-emoji-img">` after mount (the server render is always text).
7
+ */
8
+ declare function useEmojiRenderer(emojiImageBaseUrl: string | undefined): (emoji: string) => ReactNode;
9
+
10
+ export { useEmojiRenderer };
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useMemo, useState } from "react";
4
+ import { canRenderEmoji, emojiImageSrc, normalizeEmojiImageBaseUrl } from "../emoji-image.js";
5
+ function useEmojiRenderer(emojiImageBaseUrl) {
6
+ const base = useMemo(() => normalizeEmojiImageBaseUrl(emojiImageBaseUrl), [emojiImageBaseUrl]);
7
+ const [mounted, setMounted] = useState(false);
8
+ useEffect(() => {
9
+ if (base) setMounted(true);
10
+ }, [base]);
11
+ return useCallback((emoji) => {
12
+ if (!base || !mounted || canRenderEmoji(emoji)) return emoji;
13
+ return /* @__PURE__ */ jsx("img", { className: "uix-emoji-img", src: emojiImageSrc(base, emoji), alt: emoji, draggable: false, decoding: "async" });
14
+ }, [base, mounted]);
15
+ }
16
+ export {
17
+ useEmojiRenderer
18
+ };
@@ -0,0 +1,60 @@
1
+ import * as react from 'react';
2
+ import { ReactElement } from 'react';
3
+ import { Placement } from '../overlay-position.js';
4
+ import { EmojiLocale, EmojiDataLoader } from '../emoji-model.js';
5
+
6
+ interface EmojiPickerLabels {
7
+ /** Accessible name of the picker dialog. */
8
+ dialog: string;
9
+ /** Search field label and placeholder. */
10
+ search: string;
11
+ /** Heading of the recently-used section. */
12
+ recent: string;
13
+ /** Heading of the quick-pick section. */
14
+ quickPicks: string;
15
+ /** Accessible name of the category navigation. */
16
+ categories: string;
17
+ /** Shown when a search matches nothing. */
18
+ noResults: string;
19
+ /** Shown while the emoji data loads. */
20
+ loading: string;
21
+ /** Shown when the emoji data cannot be loaded. */
22
+ loadFailed: string;
23
+ }
24
+ declare const DEFAULT_EMOJI_PICKER_LABELS: EmojiPickerLabels;
25
+ interface EmojiPickerProps {
26
+ /** Called with the chosen emoji as Unicode text. The picker then closes. */
27
+ onSelect: (emoji: string) => void;
28
+ /** The button that opens the picker. It receives `aria-haspopup`, `aria-expanded` and a click handler. */
29
+ trigger: ReactElement;
30
+ labels?: Partial<EmojiPickerLabels>;
31
+ /** Language of emoji names and search terms. Default `'en'`. */
32
+ locale?: EmojiLocale;
33
+ /** Emoji shown first, before recents and categories. */
34
+ quickPicks?: ReadonlyArray<string>;
35
+ /**
36
+ * Same-origin folder of fallback PNGs for emoji this device cannot draw (see
37
+ * `emojiImageFileName` for the naming). Unset = native emoji only.
38
+ */
39
+ emojiImageBaseUrl?: string;
40
+ /**
41
+ * Replace the emojibase loader (tests, or another bundled dataset). The default loads
42
+ * a bundled chunk through dynamic `import()`; it never fetches a URL.
43
+ */
44
+ loadData?: EmojiDataLoader;
45
+ /** localStorage key for recently used emoji. */
46
+ recentStorageKey?: string;
47
+ placement?: Placement;
48
+ onOpenChange?: (open: boolean) => void;
49
+ className?: string;
50
+ }
51
+ /** Fixed column count; the CSS grid uses the same number. */
52
+ declare const EMOJI_GRID_COLUMNS = 8;
53
+ /**
54
+ * Emoji picker in a native popover: search, quick picks, recently used and the
55
+ * Unicode categories, with names in English or German. The emoji dataset loads on
56
+ * first open. The grid is one tab stop; arrow keys, Home and End move within it.
57
+ */
58
+ declare function EmojiPicker({ onSelect, trigger, labels: labelOverrides, locale, quickPicks, loadData, recentStorageKey, placement, onOpenChange, className, emojiImageBaseUrl, }: EmojiPickerProps): react.JSX.Element;
59
+
60
+ export { DEFAULT_EMOJI_PICKER_LABELS, EMOJI_GRID_COLUMNS, EmojiPicker, type EmojiPickerLabels, type EmojiPickerProps };