@tensor_1/react 2.18.0 → 2.19.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/README.md +102 -1
- package/dist/components/Comments.d.ts +18 -2
- package/dist/components/Comments.js +37 -12
- package/dist/components/EditorIcons.d.ts +39 -0
- package/dist/components/EditorIcons.js +51 -0
- package/dist/components/EmojiGlyph.d.ts +10 -0
- package/dist/components/EmojiGlyph.js +18 -0
- package/dist/components/EmojiPicker.d.ts +60 -0
- package/dist/components/EmojiPicker.js +277 -0
- package/dist/components/Markdown.d.ts +36 -0
- package/dist/components/Markdown.js +104 -0
- package/dist/components/ReactionBar.d.ts +57 -0
- package/dist/components/ReactionBar.js +81 -0
- package/dist/components/RelationshipGraph.js +23 -4
- package/dist/components/RichTextEditor.d.ts +128 -0
- package/dist/components/RichTextEditor.js +954 -0
- package/dist/components/SaveStatus.d.ts +32 -0
- package/dist/components/SaveStatus.js +21 -0
- package/dist/components/Tabs.d.ts +10 -3
- package/dist/components/Tabs.js +5 -3
- package/dist/emoji-image-WQLh8ZQP.d.cts +128 -0
- package/dist/emoji-image.d.ts +25 -0
- package/dist/emoji-image.js +80 -0
- package/dist/emoji-model.d.ts +66 -0
- package/dist/emoji-model.js +108 -0
- package/dist/emoji.cjs +871 -0
- package/dist/emoji.d.cts +57 -0
- package/dist/emoji.d.ts +6 -0
- package/dist/emoji.js +18 -0
- package/dist/index.cjs +587 -519
- package/dist/index.d.cts +59 -64
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/markdown-model.d.ts +75 -0
- package/dist/markdown-model.js +438 -0
- package/dist/markdown.cjs +585 -0
- package/dist/markdown.d.cts +115 -0
- package/dist/markdown.d.ts +4 -0
- package/dist/markdown.js +9 -0
- package/dist/overlay-position-CEGSz6Z2.d.cts +59 -0
- package/dist/rich-text/emoji-items.d.ts +14 -0
- package/dist/rich-text/emoji-items.js +7 -0
- package/dist/rich-text/pipeline.d.ts +91 -0
- package/dist/rich-text/pipeline.js +379 -0
- package/dist/rich-text/round-trip.d.ts +24 -0
- package/dist/rich-text/round-trip.js +11 -0
- package/dist/rich-text.cjs +2578 -0
- package/dist/rich-text.d.cts +148 -0
- package/dist/rich-text.d.ts +8 -0
- package/dist/rich-text.js +10 -0
- package/dist/url-policy.d.ts +9 -0
- package/dist/url-policy.js +11 -0
- 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
|
-
/**
|
|
21
|
-
|
|
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({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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,
|
|
@@ -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 };
|