@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/dist/emoji.cjs
ADDED
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/emoji.ts
|
|
31
|
+
var emoji_exports = {};
|
|
32
|
+
__export(emoji_exports, {
|
|
33
|
+
DEFAULT_EMOJI_PICKER_LABELS: () => DEFAULT_EMOJI_PICKER_LABELS,
|
|
34
|
+
DEFAULT_QUICK_REACTIONS: () => DEFAULT_QUICK_REACTIONS,
|
|
35
|
+
DEFAULT_REACTION_BAR_LABELS: () => DEFAULT_REACTION_BAR_LABELS,
|
|
36
|
+
EMOJI_GRID_COLUMNS: () => EMOJI_GRID_COLUMNS,
|
|
37
|
+
EmojiPicker: () => EmojiPicker,
|
|
38
|
+
ReactionBar: () => ReactionBar,
|
|
39
|
+
buildEmojiData: () => buildEmojiData,
|
|
40
|
+
canRenderEmoji: () => canRenderEmoji,
|
|
41
|
+
emojiImageFileName: () => emojiImageFileName,
|
|
42
|
+
loadEmojiData: () => loadEmojiData,
|
|
43
|
+
normalizeEmojiImageBaseUrl: () => normalizeEmojiImageBaseUrl,
|
|
44
|
+
searchEmoji: () => searchEmoji
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(emoji_exports);
|
|
47
|
+
|
|
48
|
+
// src/components/EmojiPicker.tsx
|
|
49
|
+
var import_react4 = require("react");
|
|
50
|
+
|
|
51
|
+
// src/cx.ts
|
|
52
|
+
function cx(...args) {
|
|
53
|
+
return args.filter(Boolean).join(" ");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/components/Popover.tsx
|
|
57
|
+
var import_react2 = require("react");
|
|
58
|
+
|
|
59
|
+
// src/hooks/useAnchoredPosition.ts
|
|
60
|
+
var import_react = require("react");
|
|
61
|
+
|
|
62
|
+
// src/overlay-position.ts
|
|
63
|
+
var OPPOSITE = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
64
|
+
function parsePlacement(p) {
|
|
65
|
+
const [side, align] = p.split("-");
|
|
66
|
+
return { side, align: align ?? "center" };
|
|
67
|
+
}
|
|
68
|
+
var clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
69
|
+
function spaceOn(side, a, vp, offset) {
|
|
70
|
+
switch (side) {
|
|
71
|
+
case "top":
|
|
72
|
+
return a.y - offset;
|
|
73
|
+
case "bottom":
|
|
74
|
+
return vp.height - (a.y + a.height) - offset;
|
|
75
|
+
case "left":
|
|
76
|
+
return a.x - offset;
|
|
77
|
+
case "right":
|
|
78
|
+
return vp.width - (a.x + a.width) - offset;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function fitsOn(side, a, f, vp, offset, padding) {
|
|
82
|
+
const need = side === "top" || side === "bottom" ? f.height : f.width;
|
|
83
|
+
return spaceOn(side, a, vp, offset) >= need + padding;
|
|
84
|
+
}
|
|
85
|
+
function computePosition(anchor, floating, viewport, options = {}) {
|
|
86
|
+
const { placement = "bottom-start", offset = 6, padding = 8, flip = true, shift = true } = options;
|
|
87
|
+
const { side: preferred, align } = parsePlacement(placement);
|
|
88
|
+
let side = preferred;
|
|
89
|
+
if (flip && !fitsOn(side, anchor, floating, viewport, offset, padding)) {
|
|
90
|
+
const opp = OPPOSITE[side];
|
|
91
|
+
if (fitsOn(opp, anchor, floating, viewport, offset, padding) || spaceOn(opp, anchor, viewport, offset) > spaceOn(side, anchor, viewport, offset)) {
|
|
92
|
+
side = opp;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let x, y;
|
|
96
|
+
const horizontal = side === "top" || side === "bottom";
|
|
97
|
+
if (side === "top") y = anchor.y - floating.height - offset;
|
|
98
|
+
else if (side === "bottom") y = anchor.y + anchor.height + offset;
|
|
99
|
+
else if (side === "left") x = anchor.x - floating.width - offset;
|
|
100
|
+
else x = anchor.x + anchor.width + offset;
|
|
101
|
+
if (horizontal) {
|
|
102
|
+
const start = anchor.x;
|
|
103
|
+
const end = anchor.x + anchor.width - floating.width;
|
|
104
|
+
const center = anchor.x + (anchor.width - floating.width) / 2;
|
|
105
|
+
x = align === "start" ? start : align === "end" ? end : center;
|
|
106
|
+
if (shift) x = clamp(x, padding, Math.max(padding, viewport.width - floating.width - padding));
|
|
107
|
+
} else {
|
|
108
|
+
const start = anchor.y;
|
|
109
|
+
const end = anchor.y + anchor.height - floating.height;
|
|
110
|
+
const center = anchor.y + (anchor.height - floating.height) / 2;
|
|
111
|
+
y = align === "start" ? start : align === "end" ? end : center;
|
|
112
|
+
if (shift) y = clamp(y, padding, Math.max(padding, viewport.height - floating.height - padding));
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
x,
|
|
116
|
+
y,
|
|
117
|
+
placement: align === "center" ? side : `${side}-${align}`,
|
|
118
|
+
side,
|
|
119
|
+
align
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/hooks/useAnchoredPosition.ts
|
|
124
|
+
var useIsomorphicLayoutEffect = typeof document !== "undefined" ? import_react.useLayoutEffect : import_react.useEffect;
|
|
125
|
+
var resolve = (a) => a && typeof a === "object" && "current" in a ? a.current : a ?? null;
|
|
126
|
+
function useAnchoredPosition(anchor, floatingRef, { open, placement = "bottom-start", offset = 6, padding = 8 }) {
|
|
127
|
+
const opts = (0, import_react.useRef)({ placement, offset, padding });
|
|
128
|
+
opts.current = { placement, offset, padding };
|
|
129
|
+
const reposition = (0, import_react.useCallback)(() => {
|
|
130
|
+
const anchorEl = resolve(anchor);
|
|
131
|
+
const floating = floatingRef.current;
|
|
132
|
+
if (!anchorEl || !floating) return;
|
|
133
|
+
const s = floating.style;
|
|
134
|
+
s.position = "fixed";
|
|
135
|
+
s.inset = "auto";
|
|
136
|
+
s.margin = "0";
|
|
137
|
+
const a = anchorEl.getBoundingClientRect();
|
|
138
|
+
const { placement: p, offset: o, padding: pad } = opts.current;
|
|
139
|
+
const pos = computePosition(
|
|
140
|
+
{ x: a.x, y: a.y, width: a.width, height: a.height },
|
|
141
|
+
{ width: floating.offsetWidth, height: floating.offsetHeight },
|
|
142
|
+
{ width: window.innerWidth, height: window.innerHeight },
|
|
143
|
+
{ placement: p, offset: o, padding: pad }
|
|
144
|
+
);
|
|
145
|
+
s.left = `${Math.round(pos.x)}px`;
|
|
146
|
+
s.top = `${Math.round(pos.y)}px`;
|
|
147
|
+
floating.dataset.placement = pos.placement;
|
|
148
|
+
}, [anchor, floatingRef]);
|
|
149
|
+
useIsomorphicLayoutEffect(() => {
|
|
150
|
+
if (!open) return;
|
|
151
|
+
reposition();
|
|
152
|
+
const onScroll = () => reposition();
|
|
153
|
+
window.addEventListener("scroll", onScroll, { passive: true, capture: true });
|
|
154
|
+
window.addEventListener("resize", onScroll);
|
|
155
|
+
return () => {
|
|
156
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
157
|
+
window.removeEventListener("resize", onScroll);
|
|
158
|
+
};
|
|
159
|
+
}, [open, reposition]);
|
|
160
|
+
return reposition;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/components/Popover.tsx
|
|
164
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
165
|
+
function Popover({
|
|
166
|
+
popover = "auto",
|
|
167
|
+
anchor,
|
|
168
|
+
placement = "bottom-start",
|
|
169
|
+
offset = 6,
|
|
170
|
+
className,
|
|
171
|
+
children,
|
|
172
|
+
...props
|
|
173
|
+
}) {
|
|
174
|
+
const ref = (0, import_react2.useRef)(null);
|
|
175
|
+
const [open, setOpen] = (0, import_react2.useState)(false);
|
|
176
|
+
const popoverAttr = { popover };
|
|
177
|
+
const reposition = useAnchoredPosition(anchor ?? null, ref, {
|
|
178
|
+
open: open && !!anchor,
|
|
179
|
+
placement,
|
|
180
|
+
offset
|
|
181
|
+
});
|
|
182
|
+
(0, import_react2.useEffect)(() => {
|
|
183
|
+
const el = ref.current;
|
|
184
|
+
if (!el || !anchor) return;
|
|
185
|
+
const sync = () => {
|
|
186
|
+
const isOpen = el.matches(":popover-open");
|
|
187
|
+
if (isOpen) reposition();
|
|
188
|
+
setOpen(isOpen);
|
|
189
|
+
};
|
|
190
|
+
el.addEventListener("toggle", sync);
|
|
191
|
+
return () => el.removeEventListener("toggle", sync);
|
|
192
|
+
}, [anchor, reposition]);
|
|
193
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref, className: cx("uix-popover", className), ...popoverAttr, ...props, children });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/emoji-model.ts
|
|
197
|
+
var COMPONENT_GROUP_KEY = "component";
|
|
198
|
+
var capitalize = (s) => s ? s[0].toLocaleUpperCase() + s.slice(1) : s;
|
|
199
|
+
function buildEmojiData(locale, compact, messages) {
|
|
200
|
+
const sourceGroups = [...messages.groups].sort((a, b) => a.order - b.order);
|
|
201
|
+
const groups = [];
|
|
202
|
+
const groupIndex = /* @__PURE__ */ new Map();
|
|
203
|
+
for (const g of sourceGroups) {
|
|
204
|
+
if (g.key === COMPONENT_GROUP_KEY) continue;
|
|
205
|
+
groupIndex.set(g.order, groups.length);
|
|
206
|
+
groups.push({ key: g.key, name: capitalize(g.message) });
|
|
207
|
+
}
|
|
208
|
+
const emojis = [];
|
|
209
|
+
const names = /* @__PURE__ */ new Map();
|
|
210
|
+
const sorted = [...compact].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
211
|
+
for (const item of sorted) {
|
|
212
|
+
if (item.group === void 0) continue;
|
|
213
|
+
const group = groupIndex.get(item.group);
|
|
214
|
+
if (group === void 0) continue;
|
|
215
|
+
const entry = { emoji: item.unicode, name: item.label, tags: item.tags ?? [], group };
|
|
216
|
+
emojis.push(entry);
|
|
217
|
+
names.set(item.unicode, item.label);
|
|
218
|
+
names.set(item.unicode.replace(/️/g, ""), item.label);
|
|
219
|
+
}
|
|
220
|
+
return { locale, groups, emojis, names };
|
|
221
|
+
}
|
|
222
|
+
var cache = /* @__PURE__ */ new Map();
|
|
223
|
+
var loadEmojiData = (locale) => {
|
|
224
|
+
let pending = cache.get(locale);
|
|
225
|
+
if (!pending) {
|
|
226
|
+
const files = locale === "de" ? Promise.all([import("emojibase-data/de/compact.json"), import("emojibase-data/de/messages.json")]) : Promise.all([import("emojibase-data/en/compact.json"), import("emojibase-data/en/messages.json")]);
|
|
227
|
+
pending = files.then(
|
|
228
|
+
([compact, messages]) => buildEmojiData(
|
|
229
|
+
locale,
|
|
230
|
+
compact.default ?? compact,
|
|
231
|
+
messages.default ?? messages
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
pending.catch(() => cache.delete(locale));
|
|
235
|
+
cache.set(locale, pending);
|
|
236
|
+
}
|
|
237
|
+
return pending;
|
|
238
|
+
};
|
|
239
|
+
var fold = (s) => s.toLocaleLowerCase().normalize("NFD").replace(/\p{M}/gu, "");
|
|
240
|
+
function searchEmoji(data, query, limit = 64) {
|
|
241
|
+
const q = fold(query.trim());
|
|
242
|
+
if (!q) return [];
|
|
243
|
+
const prefix = [];
|
|
244
|
+
const contains = [];
|
|
245
|
+
for (const entry of data.emojis) {
|
|
246
|
+
const name = fold(entry.name);
|
|
247
|
+
const words = [...name.split(/[\s:,-]+/), ...entry.tags.map(fold)];
|
|
248
|
+
if (words.some((w) => w.startsWith(q)) || name.startsWith(q)) prefix.push(entry);
|
|
249
|
+
else if (name.includes(q)) contains.push(entry);
|
|
250
|
+
if (prefix.length >= limit) break;
|
|
251
|
+
}
|
|
252
|
+
return [...prefix, ...contains].slice(0, limit);
|
|
253
|
+
}
|
|
254
|
+
var DEFAULT_RECENT_KEY = "uix-emoji-recent";
|
|
255
|
+
var RECENT_MAX = 16;
|
|
256
|
+
function readRecentEmoji(key = DEFAULT_RECENT_KEY) {
|
|
257
|
+
try {
|
|
258
|
+
const raw = globalThis.localStorage?.getItem(key);
|
|
259
|
+
const list = raw ? JSON.parse(raw) : [];
|
|
260
|
+
return Array.isArray(list) ? list.filter((e) => typeof e === "string").slice(0, RECENT_MAX) : [];
|
|
261
|
+
} catch {
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function pushRecentEmoji(emoji, key = DEFAULT_RECENT_KEY) {
|
|
266
|
+
const next = [emoji, ...readRecentEmoji(key).filter((e) => e !== emoji)].slice(0, RECENT_MAX);
|
|
267
|
+
try {
|
|
268
|
+
globalThis.localStorage?.setItem(key, JSON.stringify(next));
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
return next;
|
|
272
|
+
}
|
|
273
|
+
function emojiGridMove(index, key, count, columns) {
|
|
274
|
+
if (count === 0) return null;
|
|
275
|
+
switch (key) {
|
|
276
|
+
case "ArrowRight":
|
|
277
|
+
return Math.min(index + 1, count - 1);
|
|
278
|
+
case "ArrowLeft":
|
|
279
|
+
return Math.max(index - 1, 0);
|
|
280
|
+
case "ArrowDown":
|
|
281
|
+
return Math.min(index + columns, count - 1);
|
|
282
|
+
case "ArrowUp":
|
|
283
|
+
return index - columns < 0 ? null : index - columns;
|
|
284
|
+
case "Home":
|
|
285
|
+
return 0;
|
|
286
|
+
case "End":
|
|
287
|
+
return count - 1;
|
|
288
|
+
default:
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function formatLabel(template, values) {
|
|
293
|
+
return template.replace(/\{(\w+)\}/g, (match, name) => name in values ? String(values[name]) : match);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/components/EmojiGlyph.tsx
|
|
297
|
+
var import_react3 = require("react");
|
|
298
|
+
|
|
299
|
+
// src/emoji-image.ts
|
|
300
|
+
var warned = /* @__PURE__ */ new Set();
|
|
301
|
+
var devWarn = (message) => {
|
|
302
|
+
if (warned.has(message) || typeof process === "undefined" || process.env.NODE_ENV === "production") return;
|
|
303
|
+
warned.add(message);
|
|
304
|
+
console.warn(message);
|
|
305
|
+
};
|
|
306
|
+
function normalizeEmojiImageBaseUrl(value) {
|
|
307
|
+
if (value === void 0 || value === "") return void 0;
|
|
308
|
+
const sameOrigin = /^(?:\/(?![/\\])|\.\/)/.test(value) && !/[\\\s]/.test(value) && !/^[^/?#]*:/.test(value);
|
|
309
|
+
if (!sameOrigin) {
|
|
310
|
+
devWarn(`uix: emojiImageBaseUrl "${value}" was ignored \u2014 only same-origin paths starting with "/" or "./" are allowed.`);
|
|
311
|
+
return void 0;
|
|
312
|
+
}
|
|
313
|
+
return value.replace(/\/+$/, "") || "/";
|
|
314
|
+
}
|
|
315
|
+
function emojiImageFileName(emoji) {
|
|
316
|
+
return `${Array.from(emoji, (ch) => ch.codePointAt(0).toString(16)).join("-")}.png`;
|
|
317
|
+
}
|
|
318
|
+
function emojiImageSrc(base, emoji) {
|
|
319
|
+
return `${base === "/" ? "" : base}/${emojiImageFileName(emoji)}`;
|
|
320
|
+
}
|
|
321
|
+
var support = /* @__PURE__ */ new Map();
|
|
322
|
+
var context;
|
|
323
|
+
var SIZE = 20;
|
|
324
|
+
function canRenderEmoji(emoji) {
|
|
325
|
+
const cached = support.get(emoji);
|
|
326
|
+
if (cached !== void 0) return cached;
|
|
327
|
+
if (typeof document === "undefined") return true;
|
|
328
|
+
if (context === void 0) {
|
|
329
|
+
try {
|
|
330
|
+
const canvas = document.createElement("canvas");
|
|
331
|
+
canvas.width = SIZE;
|
|
332
|
+
canvas.height = SIZE;
|
|
333
|
+
context = typeof canvas.getContext === "function" ? canvas.getContext("2d", { willReadFrequently: true }) : null;
|
|
334
|
+
} catch {
|
|
335
|
+
context = null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (!context) return true;
|
|
339
|
+
let result = true;
|
|
340
|
+
try {
|
|
341
|
+
context.clearRect(0, 0, SIZE, SIZE);
|
|
342
|
+
context.fillStyle = "#000";
|
|
343
|
+
context.textBaseline = "top";
|
|
344
|
+
context.font = `${SIZE - 4}px sans-serif`;
|
|
345
|
+
context.fillText(emoji, 0, 0);
|
|
346
|
+
const { data } = context.getImageData(0, 0, SIZE, SIZE);
|
|
347
|
+
result = false;
|
|
348
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
349
|
+
const [r, g, b, a] = [data[i], data[i + 1], data[i + 2], data[i + 3]];
|
|
350
|
+
if (a > 0 && (Math.abs(r - g) > 12 || Math.abs(g - b) > 12)) {
|
|
351
|
+
result = true;
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
result = true;
|
|
357
|
+
}
|
|
358
|
+
support.set(emoji, result);
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/components/EmojiGlyph.tsx
|
|
363
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
364
|
+
function useEmojiRenderer(emojiImageBaseUrl) {
|
|
365
|
+
const base = (0, import_react3.useMemo)(() => normalizeEmojiImageBaseUrl(emojiImageBaseUrl), [emojiImageBaseUrl]);
|
|
366
|
+
const [mounted, setMounted] = (0, import_react3.useState)(false);
|
|
367
|
+
(0, import_react3.useEffect)(() => {
|
|
368
|
+
if (base) setMounted(true);
|
|
369
|
+
}, [base]);
|
|
370
|
+
return (0, import_react3.useCallback)((emoji) => {
|
|
371
|
+
if (!base || !mounted || canRenderEmoji(emoji)) return emoji;
|
|
372
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { className: "uix-emoji-img", src: emojiImageSrc(base, emoji), alt: emoji, draggable: false, decoding: "async" });
|
|
373
|
+
}, [base, mounted]);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/components/EmojiPicker.tsx
|
|
377
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
378
|
+
var DEFAULT_EMOJI_PICKER_LABELS = {
|
|
379
|
+
dialog: "Choose an emoji",
|
|
380
|
+
search: "Search emoji",
|
|
381
|
+
recent: "Recently used",
|
|
382
|
+
quickPicks: "Frequently used",
|
|
383
|
+
categories: "Emoji categories",
|
|
384
|
+
noResults: "No emoji found",
|
|
385
|
+
loading: "Loading emoji\u2026",
|
|
386
|
+
loadFailed: "Emoji could not be loaded."
|
|
387
|
+
};
|
|
388
|
+
var EMOJI_GRID_COLUMNS = 8;
|
|
389
|
+
function EmojiPicker({
|
|
390
|
+
onSelect,
|
|
391
|
+
trigger,
|
|
392
|
+
labels: labelOverrides,
|
|
393
|
+
locale = "en",
|
|
394
|
+
quickPicks,
|
|
395
|
+
loadData = loadEmojiData,
|
|
396
|
+
recentStorageKey = DEFAULT_RECENT_KEY,
|
|
397
|
+
placement = "bottom-start",
|
|
398
|
+
onOpenChange,
|
|
399
|
+
className,
|
|
400
|
+
emojiImageBaseUrl
|
|
401
|
+
}) {
|
|
402
|
+
const renderEmoji = useEmojiRenderer(emojiImageBaseUrl);
|
|
403
|
+
const labels = { ...DEFAULT_EMOJI_PICKER_LABELS, ...labelOverrides };
|
|
404
|
+
const id = (0, import_react4.useId)();
|
|
405
|
+
const popoverId = `${id}-picker`;
|
|
406
|
+
const anchorRef = (0, import_react4.useRef)(null);
|
|
407
|
+
const searchRef = (0, import_react4.useRef)(null);
|
|
408
|
+
const gridRef = (0, import_react4.useRef)(null);
|
|
409
|
+
const pointerWasOpen = (0, import_react4.useRef)(null);
|
|
410
|
+
const [open, setOpen] = (0, import_react4.useState)(false);
|
|
411
|
+
const [data, setData] = (0, import_react4.useState)(null);
|
|
412
|
+
const [failed, setFailed] = (0, import_react4.useState)(false);
|
|
413
|
+
const [query, setQuery] = (0, import_react4.useState)("");
|
|
414
|
+
const [recent, setRecent] = (0, import_react4.useState)([]);
|
|
415
|
+
const [active, setActive] = (0, import_react4.useState)(0);
|
|
416
|
+
const popoverEl = () => typeof document === "undefined" ? null : document.getElementById(popoverId);
|
|
417
|
+
const isOpen = (el) => {
|
|
418
|
+
try {
|
|
419
|
+
return !!el?.matches(":popover-open");
|
|
420
|
+
} catch {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
(0, import_react4.useEffect)(() => {
|
|
425
|
+
const el = popoverEl();
|
|
426
|
+
if (!el) return;
|
|
427
|
+
const onToggle = (event) => {
|
|
428
|
+
const { newState } = event;
|
|
429
|
+
const nowOpen = newState ? newState === "open" : isOpen(el);
|
|
430
|
+
setOpen(nowOpen);
|
|
431
|
+
onOpenChange?.(nowOpen);
|
|
432
|
+
if (nowOpen) {
|
|
433
|
+
setRecent(readRecentEmoji(recentStorageKey));
|
|
434
|
+
setQuery("");
|
|
435
|
+
setActive(0);
|
|
436
|
+
requestAnimationFrame(() => searchRef.current?.focus());
|
|
437
|
+
} else if (el.contains(document.activeElement) || document.activeElement === document.body) {
|
|
438
|
+
focusTrigger();
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
el.addEventListener("toggle", onToggle);
|
|
442
|
+
return () => el.removeEventListener("toggle", onToggle);
|
|
443
|
+
}, [popoverId, recentStorageKey, onOpenChange]);
|
|
444
|
+
(0, import_react4.useEffect)(() => {
|
|
445
|
+
if (!open || data?.locale === locale) return;
|
|
446
|
+
let live = true;
|
|
447
|
+
setFailed(false);
|
|
448
|
+
loadData(locale).then(
|
|
449
|
+
(loaded) => {
|
|
450
|
+
if (live) setData(loaded);
|
|
451
|
+
},
|
|
452
|
+
() => {
|
|
453
|
+
if (live) setFailed(true);
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
return () => {
|
|
457
|
+
live = false;
|
|
458
|
+
};
|
|
459
|
+
}, [open, locale, loadData, data?.locale]);
|
|
460
|
+
const focusTrigger = () => {
|
|
461
|
+
const el = anchorRef.current?.querySelector("button, [href], [tabindex]");
|
|
462
|
+
el?.focus();
|
|
463
|
+
};
|
|
464
|
+
const hide = () => {
|
|
465
|
+
const el = popoverEl();
|
|
466
|
+
if (el && isOpen(el)) el.hidePopover();
|
|
467
|
+
};
|
|
468
|
+
const sections = (0, import_react4.useMemo)(() => {
|
|
469
|
+
if (query.trim()) {
|
|
470
|
+
return data ? [{ key: "results", title: labels.search, emojis: searchEmoji(data, query).map((e) => e.emoji) }] : [];
|
|
471
|
+
}
|
|
472
|
+
const list = [];
|
|
473
|
+
if (quickPicks?.length) list.push({ key: "quick", title: labels.quickPicks, emojis: [...new Set(quickPicks)] });
|
|
474
|
+
if (recent.length) list.push({ key: "recent", title: labels.recent, emojis: recent });
|
|
475
|
+
if (data) {
|
|
476
|
+
data.groups.forEach((group, index) => {
|
|
477
|
+
list.push({ key: group.key, title: group.name, emojis: data.emojis.filter((e) => e.group === index).map((e) => e.emoji) });
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
return list;
|
|
481
|
+
}, [query, data, quickPicks, recent, labels.search, labels.quickPicks, labels.recent]);
|
|
482
|
+
const flat = (0, import_react4.useMemo)(() => sections.flatMap((s) => s.emojis), [sections]);
|
|
483
|
+
const sectionStart = (0, import_react4.useMemo)(() => {
|
|
484
|
+
const starts = /* @__PURE__ */ new Map();
|
|
485
|
+
let at = 0;
|
|
486
|
+
for (const section of sections) {
|
|
487
|
+
starts.set(section.key, at);
|
|
488
|
+
at += section.emojis.length;
|
|
489
|
+
}
|
|
490
|
+
return starts;
|
|
491
|
+
}, [sections]);
|
|
492
|
+
const activeIndex = Math.min(active, Math.max(flat.length - 1, 0));
|
|
493
|
+
const choose = (emoji) => {
|
|
494
|
+
setRecent(pushRecentEmoji(emoji, recentStorageKey));
|
|
495
|
+
onSelect(emoji);
|
|
496
|
+
hide();
|
|
497
|
+
};
|
|
498
|
+
const focusCell = (0, import_react4.useCallback)((index) => {
|
|
499
|
+
setActive(index);
|
|
500
|
+
const find = () => gridRef.current?.querySelector(`[data-emoji-index="${index}"]`);
|
|
501
|
+
const cell = find();
|
|
502
|
+
if (cell) cell.focus();
|
|
503
|
+
else requestAnimationFrame(() => find()?.focus());
|
|
504
|
+
}, []);
|
|
505
|
+
const onGridKeyDown = (event) => {
|
|
506
|
+
const target = event.target;
|
|
507
|
+
const index = Number(target.dataset.emojiIndex ?? -1);
|
|
508
|
+
if (index < 0) return;
|
|
509
|
+
const next = emojiGridMove(index, event.key, flat.length, EMOJI_GRID_COLUMNS);
|
|
510
|
+
if (event.key === "ArrowUp" && next === null) {
|
|
511
|
+
event.preventDefault();
|
|
512
|
+
searchRef.current?.focus();
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (next === null) return;
|
|
516
|
+
event.preventDefault();
|
|
517
|
+
focusCell(next);
|
|
518
|
+
};
|
|
519
|
+
const onSearchKeyDown = (event) => {
|
|
520
|
+
if (event.key === "Escape") {
|
|
521
|
+
event.preventDefault();
|
|
522
|
+
hide();
|
|
523
|
+
} else if (event.key === "ArrowDown" && flat.length) {
|
|
524
|
+
event.preventDefault();
|
|
525
|
+
focusCell(0);
|
|
526
|
+
} else if (event.key === "Enter" && query.trim() && flat[0]) {
|
|
527
|
+
event.preventDefault();
|
|
528
|
+
choose(flat[0]);
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
const toggleFromTrigger = (event) => {
|
|
532
|
+
const el = popoverEl();
|
|
533
|
+
if (!el || event.defaultPrevented) return;
|
|
534
|
+
const was = pointerWasOpen.current;
|
|
535
|
+
pointerWasOpen.current = null;
|
|
536
|
+
if (was === true) return;
|
|
537
|
+
if (isOpen(el)) el.hidePopover();
|
|
538
|
+
else el.showPopover();
|
|
539
|
+
};
|
|
540
|
+
const triggerProps = (0, import_react4.isValidElement)(trigger) ? trigger.props : {};
|
|
541
|
+
const triggerEl = (0, import_react4.isValidElement)(trigger) ? (0, import_react4.cloneElement)(trigger, {
|
|
542
|
+
"aria-haspopup": "dialog",
|
|
543
|
+
"aria-expanded": open,
|
|
544
|
+
"aria-controls": popoverId,
|
|
545
|
+
onPointerDown: (event) => {
|
|
546
|
+
pointerWasOpen.current = isOpen(popoverEl());
|
|
547
|
+
triggerProps.onPointerDown?.(event);
|
|
548
|
+
},
|
|
549
|
+
onClick: (event) => {
|
|
550
|
+
triggerProps.onClick?.(event);
|
|
551
|
+
toggleFromTrigger(event);
|
|
552
|
+
}
|
|
553
|
+
}) : trigger;
|
|
554
|
+
const names = data?.names;
|
|
555
|
+
let offset = 0;
|
|
556
|
+
const status = failed ? labels.loadFailed : !data && open ? labels.loading : query.trim() && data && flat.length === 0 ? labels.noResults : "";
|
|
557
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { ref: anchorRef, className: cx("uix-emoji-picker__anchor", className), children: [
|
|
558
|
+
triggerEl,
|
|
559
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
560
|
+
Popover,
|
|
561
|
+
{
|
|
562
|
+
id: popoverId,
|
|
563
|
+
anchor: anchorRef,
|
|
564
|
+
placement,
|
|
565
|
+
className: "uix-emoji-picker-popover",
|
|
566
|
+
role: "dialog",
|
|
567
|
+
"aria-label": labels.dialog,
|
|
568
|
+
children: open ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "uix-emoji-picker uix-emoji-picker--full", lang: locale, children: [
|
|
569
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
570
|
+
"input",
|
|
571
|
+
{
|
|
572
|
+
ref: searchRef,
|
|
573
|
+
type: "search",
|
|
574
|
+
className: "uix-input uix-emoji-picker__search",
|
|
575
|
+
"aria-label": labels.search,
|
|
576
|
+
placeholder: labels.search,
|
|
577
|
+
value: query,
|
|
578
|
+
onChange: (event) => {
|
|
579
|
+
setQuery(event.target.value);
|
|
580
|
+
setActive(0);
|
|
581
|
+
},
|
|
582
|
+
onKeyDown: onSearchKeyDown,
|
|
583
|
+
autoComplete: "off",
|
|
584
|
+
spellCheck: false
|
|
585
|
+
}
|
|
586
|
+
),
|
|
587
|
+
data && !query.trim() ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "uix-emoji-picker__nav", role: "group", "aria-label": labels.categories, children: sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
588
|
+
"button",
|
|
589
|
+
{
|
|
590
|
+
type: "button",
|
|
591
|
+
className: "uix-emoji-picker__nav-btn",
|
|
592
|
+
"aria-label": section.title,
|
|
593
|
+
title: section.title,
|
|
594
|
+
onClick: () => {
|
|
595
|
+
const start = section.emojis.length ? sectionStart.get(section.key) ?? -1 : -1;
|
|
596
|
+
gridRef.current?.querySelector(`[data-section="${section.key}"]`)?.scrollIntoView({ block: "start" });
|
|
597
|
+
if (start >= 0) focusCell(start);
|
|
598
|
+
},
|
|
599
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-hidden": "true", children: section.emojis[0] ? renderEmoji(section.emojis[0]) : null })
|
|
600
|
+
},
|
|
601
|
+
section.key
|
|
602
|
+
)) }) : null,
|
|
603
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "uix-emoji-picker__grid", ref: gridRef, onKeyDown: onGridKeyDown, children: sections.map((section) => {
|
|
604
|
+
const start = offset;
|
|
605
|
+
offset += section.emojis.length;
|
|
606
|
+
if (!section.emojis.length) return null;
|
|
607
|
+
const headingId = `${id}-${section.key}`;
|
|
608
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "uix-emoji-picker__section", "data-section": section.key, "aria-labelledby": headingId, children: [
|
|
609
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { id: headingId, className: "uix-emoji-picker__heading", children: section.title }),
|
|
610
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "uix-emoji-picker__cells", role: "group", "aria-labelledby": headingId, children: section.emojis.map((emoji, i) => {
|
|
611
|
+
const index = start + i;
|
|
612
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
613
|
+
"button",
|
|
614
|
+
{
|
|
615
|
+
type: "button",
|
|
616
|
+
className: "uix-emoji-picker__btn",
|
|
617
|
+
"data-emoji-index": index,
|
|
618
|
+
tabIndex: index === activeIndex ? 0 : -1,
|
|
619
|
+
"aria-label": names?.get(emoji) ?? emoji,
|
|
620
|
+
title: names?.get(emoji),
|
|
621
|
+
onClick: () => choose(emoji),
|
|
622
|
+
onFocus: () => setActive(index),
|
|
623
|
+
children: renderEmoji(emoji)
|
|
624
|
+
},
|
|
625
|
+
`${section.key}-${emoji}`
|
|
626
|
+
);
|
|
627
|
+
}) })
|
|
628
|
+
] }, section.key);
|
|
629
|
+
}) }),
|
|
630
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "uix-emoji-picker__status", role: "status", children: status })
|
|
631
|
+
] }) : null
|
|
632
|
+
}
|
|
633
|
+
)
|
|
634
|
+
] });
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/components/EditorIcons.tsx
|
|
638
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
639
|
+
var PATHS = {
|
|
640
|
+
heading1: "M4 12h8M4 18V6M12 18V6M17 12l3-2v8",
|
|
641
|
+
heading2: "M4 12h8M4 18V6M12 18V6M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1",
|
|
642
|
+
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",
|
|
643
|
+
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",
|
|
644
|
+
italic: "M19 4h-9M14 20H5M15 4 9 20",
|
|
645
|
+
strike: "M16 4H9a3 3 0 0 0-2.83 4M14 12a4 4 0 0 1 0 8H6M4 12h16",
|
|
646
|
+
code: "m16 18 6-6-6-6M8 6l-6 6 6 6",
|
|
647
|
+
bulletList: "M3 12h.01M3 18h.01M3 6h.01M8 12h13M8 18h13M8 6h13",
|
|
648
|
+
orderedList: "M10 12h11M10 18h11M10 6h11M4 10h2M4 6h1v4M6 18H4c0-1 2-2 2-3s-1-1.5-2-1",
|
|
649
|
+
taskList: "m3 17 2 2 4-4M3 7l2 2 4-4M13 6h8M13 12h8M13 18h8",
|
|
650
|
+
blockquote: "M17 6H3M21 12H8M21 18H8M3 12v6",
|
|
651
|
+
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",
|
|
652
|
+
horizontalRule: "M5 12h14",
|
|
653
|
+
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",
|
|
654
|
+
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",
|
|
655
|
+
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",
|
|
656
|
+
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",
|
|
657
|
+
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",
|
|
658
|
+
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",
|
|
659
|
+
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",
|
|
660
|
+
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",
|
|
661
|
+
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",
|
|
662
|
+
addReaction: "M22 11v1a10 10 0 1 1-9-10M8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01M16 5h6M19 2v6",
|
|
663
|
+
undo: "M9 14 4 9l5-5M4 9h10.5a5.5 5.5 0 0 1 0 11H11",
|
|
664
|
+
redo: "m15 14 5-5-5-5M20 9H9.5a5.5 5.5 0 0 0 0 11H13",
|
|
665
|
+
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",
|
|
666
|
+
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",
|
|
667
|
+
clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20ZM12 6v6l4 2"
|
|
668
|
+
};
|
|
669
|
+
function EditorIcon({ name, ...props }) {
|
|
670
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
671
|
+
"svg",
|
|
672
|
+
{
|
|
673
|
+
viewBox: "0 0 24 24",
|
|
674
|
+
fill: "none",
|
|
675
|
+
stroke: "currentColor",
|
|
676
|
+
strokeWidth: 2,
|
|
677
|
+
strokeLinecap: "round",
|
|
678
|
+
strokeLinejoin: "round",
|
|
679
|
+
"aria-hidden": "true",
|
|
680
|
+
focusable: "false",
|
|
681
|
+
...props,
|
|
682
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: PATHS[name] })
|
|
683
|
+
}
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/components/Tooltip.tsx
|
|
688
|
+
var import_react5 = require("react");
|
|
689
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
690
|
+
function Tooltip({ label, placement = "top", className, children, ...props }) {
|
|
691
|
+
const triggerRef = (0, import_react5.useRef)(null);
|
|
692
|
+
const bubbleRef = (0, import_react5.useRef)(null);
|
|
693
|
+
const closeTimer = (0, import_react5.useRef)(null);
|
|
694
|
+
const [open, setOpen] = (0, import_react5.useState)(false);
|
|
695
|
+
const id = (0, import_react5.useId)();
|
|
696
|
+
useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
|
|
697
|
+
const cancelClose = () => {
|
|
698
|
+
if (closeTimer.current != null) {
|
|
699
|
+
clearTimeout(closeTimer.current);
|
|
700
|
+
closeTimer.current = null;
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
const show = () => {
|
|
704
|
+
cancelClose();
|
|
705
|
+
const b = bubbleRef.current;
|
|
706
|
+
if (b && !b.matches(":popover-open")) {
|
|
707
|
+
try {
|
|
708
|
+
b.showPopover();
|
|
709
|
+
} catch {
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
setOpen(true);
|
|
713
|
+
};
|
|
714
|
+
const hide = () => {
|
|
715
|
+
cancelClose();
|
|
716
|
+
const b = bubbleRef.current;
|
|
717
|
+
if (b && b.matches(":popover-open")) {
|
|
718
|
+
try {
|
|
719
|
+
b.hidePopover();
|
|
720
|
+
} catch {
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
setOpen(false);
|
|
724
|
+
};
|
|
725
|
+
const scheduleHide = () => {
|
|
726
|
+
cancelClose();
|
|
727
|
+
closeTimer.current = window.setTimeout(hide, 100);
|
|
728
|
+
};
|
|
729
|
+
(0, import_react5.useEffect)(() => {
|
|
730
|
+
if (!open) return;
|
|
731
|
+
const onKey = (e) => {
|
|
732
|
+
if (e.key === "Escape") hide();
|
|
733
|
+
};
|
|
734
|
+
document.addEventListener("keydown", onKey);
|
|
735
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
736
|
+
}, [open]);
|
|
737
|
+
(0, import_react5.useEffect)(() => () => {
|
|
738
|
+
if (closeTimer.current != null) clearTimeout(closeTimer.current);
|
|
739
|
+
const b = bubbleRef.current;
|
|
740
|
+
if (b && b.matches(":popover-open")) {
|
|
741
|
+
try {
|
|
742
|
+
b.hidePopover();
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}, []);
|
|
747
|
+
const bubbleAttr = { popover: "manual" };
|
|
748
|
+
const single = (0, import_react5.isValidElement)(children);
|
|
749
|
+
const child = single ? (0, import_react5.cloneElement)(children, {
|
|
750
|
+
"aria-describedby": [children.props["aria-describedby"], id].filter(Boolean).join(" ")
|
|
751
|
+
}) : children;
|
|
752
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
753
|
+
"span",
|
|
754
|
+
{
|
|
755
|
+
ref: triggerRef,
|
|
756
|
+
className: cx("uix-tooltip", className),
|
|
757
|
+
"aria-describedby": single ? void 0 : id,
|
|
758
|
+
tabIndex: single ? void 0 : 0,
|
|
759
|
+
onMouseEnter: show,
|
|
760
|
+
onMouseLeave: scheduleHide,
|
|
761
|
+
onFocus: show,
|
|
762
|
+
onBlur: hide,
|
|
763
|
+
onKeyDown: (e) => {
|
|
764
|
+
if (e.key === "Escape") hide();
|
|
765
|
+
},
|
|
766
|
+
...props,
|
|
767
|
+
children: [
|
|
768
|
+
child,
|
|
769
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
770
|
+
"span",
|
|
771
|
+
{
|
|
772
|
+
ref: bubbleRef,
|
|
773
|
+
id,
|
|
774
|
+
role: "tooltip",
|
|
775
|
+
className: "uix-tooltip__bubble",
|
|
776
|
+
onMouseEnter: cancelClose,
|
|
777
|
+
onMouseLeave: scheduleHide,
|
|
778
|
+
...bubbleAttr,
|
|
779
|
+
children: label
|
|
780
|
+
}
|
|
781
|
+
)
|
|
782
|
+
]
|
|
783
|
+
}
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/components/ReactionBar.tsx
|
|
788
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
789
|
+
var DEFAULT_REACTION_BAR_LABELS = {
|
|
790
|
+
group: "Reactions",
|
|
791
|
+
addReaction: "Add reaction",
|
|
792
|
+
reactedWith: "{names} reacted with {emoji}",
|
|
793
|
+
reactedCount: "{count} reacted with {emoji}",
|
|
794
|
+
others: "and {count} more"
|
|
795
|
+
};
|
|
796
|
+
var DEFAULT_QUICK_REACTIONS = ["\u{1F44D}", "\u2705", "\u{1F440}", "\u{1F389}", "\u2764\uFE0F"];
|
|
797
|
+
function ReactionBar({
|
|
798
|
+
reactions,
|
|
799
|
+
onToggle,
|
|
800
|
+
disabled = false,
|
|
801
|
+
quickPicks = DEFAULT_QUICK_REACTIONS,
|
|
802
|
+
labels: labelOverrides,
|
|
803
|
+
pickerLabels,
|
|
804
|
+
locale,
|
|
805
|
+
loadData,
|
|
806
|
+
maxNames = 10,
|
|
807
|
+
className,
|
|
808
|
+
emojiImageBaseUrl
|
|
809
|
+
}) {
|
|
810
|
+
const renderEmoji = useEmojiRenderer(emojiImageBaseUrl);
|
|
811
|
+
const labels = { ...DEFAULT_REACTION_BAR_LABELS, ...labelOverrides };
|
|
812
|
+
const describe = (reaction) => {
|
|
813
|
+
if (reaction.names.length === 0) {
|
|
814
|
+
return formatLabel(labels.reactedCount, { count: reaction.count, emoji: reaction.emoji });
|
|
815
|
+
}
|
|
816
|
+
const shown = reaction.names.slice(0, maxNames).join(", ");
|
|
817
|
+
const hidden = Math.max(reaction.count, reaction.names.length) - Math.min(reaction.names.length, maxNames);
|
|
818
|
+
const names = hidden > 0 ? `${shown} ${formatLabel(labels.others, { count: hidden })}` : shown;
|
|
819
|
+
return formatLabel(labels.reactedWith, { names, emoji: reaction.emoji, count: reaction.count });
|
|
820
|
+
};
|
|
821
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: cx("uix-reactions", className), role: "group", "aria-label": labels.group, children: [
|
|
822
|
+
reactions.filter((r) => r.count > 0).map((reaction) => {
|
|
823
|
+
const text = describe(reaction);
|
|
824
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Tooltip, { label: text, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
825
|
+
"button",
|
|
826
|
+
{
|
|
827
|
+
type: "button",
|
|
828
|
+
className: "uix-reaction",
|
|
829
|
+
"aria-pressed": reaction.reactedByMe,
|
|
830
|
+
"aria-label": text,
|
|
831
|
+
"data-mine": reaction.reactedByMe || void 0,
|
|
832
|
+
disabled,
|
|
833
|
+
onClick: () => onToggle(reaction.emoji),
|
|
834
|
+
children: [
|
|
835
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-hidden": "true", children: renderEmoji(reaction.emoji) }),
|
|
836
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "uix-reaction__count", "aria-hidden": "true", children: reaction.count })
|
|
837
|
+
]
|
|
838
|
+
}
|
|
839
|
+
) }, reaction.emoji);
|
|
840
|
+
}),
|
|
841
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
842
|
+
EmojiPicker,
|
|
843
|
+
{
|
|
844
|
+
quickPicks,
|
|
845
|
+
labels: pickerLabels,
|
|
846
|
+
locale,
|
|
847
|
+
loadData,
|
|
848
|
+
emojiImageBaseUrl,
|
|
849
|
+
onSelect: (emoji) => {
|
|
850
|
+
if (!reactions.some((r) => r.emoji === emoji && r.reactedByMe)) onToggle(emoji);
|
|
851
|
+
},
|
|
852
|
+
trigger: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "uix-reaction-add", "aria-label": labels.addReaction, title: labels.addReaction, disabled, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(EditorIcon, { name: "addReaction" }) })
|
|
853
|
+
}
|
|
854
|
+
)
|
|
855
|
+
] });
|
|
856
|
+
}
|
|
857
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
858
|
+
0 && (module.exports = {
|
|
859
|
+
DEFAULT_EMOJI_PICKER_LABELS,
|
|
860
|
+
DEFAULT_QUICK_REACTIONS,
|
|
861
|
+
DEFAULT_REACTION_BAR_LABELS,
|
|
862
|
+
EMOJI_GRID_COLUMNS,
|
|
863
|
+
EmojiPicker,
|
|
864
|
+
ReactionBar,
|
|
865
|
+
buildEmojiData,
|
|
866
|
+
canRenderEmoji,
|
|
867
|
+
emojiImageFileName,
|
|
868
|
+
loadEmojiData,
|
|
869
|
+
normalizeEmojiImageBaseUrl,
|
|
870
|
+
searchEmoji
|
|
871
|
+
});
|