@x-plat/design-system 0.5.48 → 0.5.50

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.
@@ -0,0 +1,727 @@
1
+ // src/components/Editor/Editor.tsx
2
+ import React from "react";
3
+
4
+ // ../../node_modules/clsx/dist/clsx.mjs
5
+ function r(e) {
6
+ var t, f, n = "";
7
+ if ("string" == typeof e || "number" == typeof e) n += e;
8
+ else if ("object" == typeof e) if (Array.isArray(e)) {
9
+ var o = e.length;
10
+ for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
11
+ } else for (f in e) e[f] && (n && (n += " "), n += f);
12
+ return n;
13
+ }
14
+ function clsx() {
15
+ for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
16
+ return n;
17
+ }
18
+ var clsx_default = clsx;
19
+
20
+ // src/components/Editor/Editor.tsx
21
+ import { jsx, jsxs } from "react/jsx-runtime";
22
+ var DEFAULT_TOOLBAR = [
23
+ "bold",
24
+ "italic",
25
+ "underline",
26
+ "strikethrough",
27
+ "code",
28
+ "heading",
29
+ "list",
30
+ "ordered-list",
31
+ "blockquote",
32
+ "code-block",
33
+ "link",
34
+ "image",
35
+ "divider"
36
+ ];
37
+ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
38
+ "p",
39
+ "br",
40
+ "h1",
41
+ "h2",
42
+ "h3",
43
+ "h4",
44
+ "h5",
45
+ "h6",
46
+ "ul",
47
+ "ol",
48
+ "li",
49
+ "blockquote",
50
+ "pre",
51
+ "code",
52
+ "strong",
53
+ "b",
54
+ "em",
55
+ "i",
56
+ "u",
57
+ "s",
58
+ "strike",
59
+ "del",
60
+ "a",
61
+ "img",
62
+ "hr",
63
+ "span",
64
+ "div"
65
+ ]);
66
+ var ALLOWED_ATTRS = {
67
+ a: ["href", "target", "rel"],
68
+ img: ["src", "alt"]
69
+ };
70
+ var sanitizeHtml = (input) => {
71
+ const doc = new DOMParser().parseFromString(`<div>${input}</div>`, "text/html");
72
+ const root = doc.body.firstElementChild;
73
+ if (!root) return "";
74
+ const walk = (node) => {
75
+ const children = Array.from(node.children);
76
+ for (const child of children) {
77
+ const tag = child.tagName.toLowerCase();
78
+ if (!ALLOWED_TAGS.has(tag)) {
79
+ while (child.firstChild) node.insertBefore(child.firstChild, child);
80
+ node.removeChild(child);
81
+ continue;
82
+ }
83
+ const allowed = ALLOWED_ATTRS[tag] || [];
84
+ for (const attr of Array.from(child.attributes)) {
85
+ if (!allowed.includes(attr.name)) {
86
+ child.removeAttribute(attr.name);
87
+ }
88
+ if (attr.name === "href" || attr.name === "src") {
89
+ const val = attr.value.trim().toLowerCase();
90
+ if (val.startsWith("javascript:") || val.startsWith("data:text/html")) {
91
+ child.removeAttribute(attr.name);
92
+ }
93
+ }
94
+ }
95
+ walk(child);
96
+ }
97
+ };
98
+ walk(root);
99
+ return root.innerHTML;
100
+ };
101
+ var escapeHtml = (text) => {
102
+ const div = document.createElement("div");
103
+ div.textContent = text;
104
+ return div.innerHTML.replace(/\n/g, "<br>");
105
+ };
106
+ var SLASH_ITEMS = [
107
+ { key: "paragraph", label: "\uB2E8\uB77D", hint: "\uAE30\uBCF8 \uD14D\uC2A4\uD2B8" },
108
+ { key: "heading", label: "\uC81C\uBAA9 1", hint: "\uD070 \uC81C\uBAA9" },
109
+ { key: "list", label: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D", hint: "\u2022 \uD56D\uBAA9" },
110
+ { key: "ordered-list", label: "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D", hint: "1. \uD56D\uBAA9" },
111
+ { key: "blockquote", label: "\uC778\uC6A9", hint: "" },
112
+ { key: "code-block", label: "\uCF54\uB4DC \uBE14\uB85D", hint: "" },
113
+ { key: "divider", label: "\uAD6C\uBD84\uC120", hint: "\u2500" },
114
+ { key: "image", label: "\uC774\uBBF8\uC9C0", hint: "" }
115
+ ];
116
+ var Editor = (props) => {
117
+ const {
118
+ value = "",
119
+ onChange,
120
+ placeholder = "\uB0B4\uC6A9\uC744 \uC785\uB825\uD558\uC138\uC694",
121
+ readOnly = false,
122
+ toolbar = DEFAULT_TOOLBAR,
123
+ enableSlashCommand = true,
124
+ enableMarkdownShortcuts = true,
125
+ onImageUpload,
126
+ minHeight = 200
127
+ } = props;
128
+ const editorRef = React.useRef(null);
129
+ const lastRangeRef = React.useRef(null);
130
+ const composingRef = React.useRef(false);
131
+ const [isFocused, setIsFocused] = React.useState(false);
132
+ const [isEmpty, setIsEmpty] = React.useState(!value);
133
+ const [activeFormats, setActiveFormats] = React.useState(/* @__PURE__ */ new Set());
134
+ const [slashOpen, setSlashOpen] = React.useState(false);
135
+ const [slashFilter, setSlashFilter] = React.useState("");
136
+ const [slashIdx, setSlashIdx] = React.useState(0);
137
+ const [slashPos, setSlashPos] = React.useState({ top: 0, left: 0 });
138
+ const slashStartRangeRef = React.useRef(null);
139
+ const [linkOpen, setLinkOpen] = React.useState(false);
140
+ const [linkValue, setLinkValue] = React.useState("");
141
+ const [linkPos, setLinkPos] = React.useState({ top: 0, left: 0 });
142
+ React.useEffect(() => {
143
+ if (!editorRef.current) return;
144
+ if (editorRef.current.innerHTML !== value) {
145
+ editorRef.current.innerHTML = sanitizeHtml(value || "");
146
+ updateEmpty();
147
+ }
148
+ }, [value]);
149
+ const updateEmpty = () => {
150
+ const el = editorRef.current;
151
+ if (!el) return;
152
+ const text = el.textContent || "";
153
+ const childCount = el.children.length;
154
+ setIsEmpty(text.length === 0 && childCount <= 1);
155
+ };
156
+ const saveSelection = () => {
157
+ const sel = window.getSelection();
158
+ if (sel && sel.rangeCount > 0 && editorRef.current?.contains(sel.anchorNode)) {
159
+ lastRangeRef.current = sel.getRangeAt(0).cloneRange();
160
+ }
161
+ };
162
+ const restoreSelection = () => {
163
+ if (!lastRangeRef.current) return;
164
+ const sel = window.getSelection();
165
+ sel?.removeAllRanges();
166
+ sel?.addRange(lastRangeRef.current);
167
+ };
168
+ const updateActiveFormats = () => {
169
+ if (!editorRef.current) return;
170
+ const sel = window.getSelection();
171
+ if (!sel || !editorRef.current.contains(sel.anchorNode)) return;
172
+ const next = /* @__PURE__ */ new Set();
173
+ try {
174
+ if (document.queryCommandState("bold")) next.add("bold");
175
+ if (document.queryCommandState("italic")) next.add("italic");
176
+ if (document.queryCommandState("underline")) next.add("underline");
177
+ if (document.queryCommandState("strikethrough")) next.add("strikethrough");
178
+ if (document.queryCommandState("insertUnorderedList")) next.add("list");
179
+ if (document.queryCommandState("insertOrderedList")) next.add("ordered-list");
180
+ const block = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
181
+ if (block) next.add(`block:${block}`);
182
+ } catch {
183
+ }
184
+ setActiveFormats(next);
185
+ };
186
+ const exec = (cmd, val) => {
187
+ document.execCommand(cmd, false, val);
188
+ editorRef.current?.focus();
189
+ updateActiveFormats();
190
+ handleInput();
191
+ };
192
+ const setBlock = (tag) => {
193
+ exec("formatBlock", tag);
194
+ };
195
+ const insertDivider = () => {
196
+ exec("insertHorizontalRule");
197
+ const el = editorRef.current;
198
+ if (el && el.lastElementChild?.tagName === "HR") {
199
+ const p = document.createElement("p");
200
+ p.innerHTML = "<br>";
201
+ el.appendChild(p);
202
+ handleInput();
203
+ }
204
+ };
205
+ const insertImageByUrl = (url) => {
206
+ if (!url) return;
207
+ exec("insertImage", url);
208
+ };
209
+ const triggerImageUpload = async () => {
210
+ if (!onImageUpload) {
211
+ const url = window.prompt("\uC774\uBBF8\uC9C0 URL");
212
+ if (url) {
213
+ restoreSelection();
214
+ insertImageByUrl(url);
215
+ }
216
+ return;
217
+ }
218
+ const input = document.createElement("input");
219
+ input.type = "file";
220
+ input.accept = "image/*";
221
+ input.onchange = async () => {
222
+ const file = input.files?.[0];
223
+ if (!file) return;
224
+ try {
225
+ const url = await onImageUpload(file);
226
+ restoreSelection();
227
+ insertImageByUrl(url);
228
+ } catch (err) {
229
+ console.error("\uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB4DC \uC2E4\uD328:", err);
230
+ }
231
+ };
232
+ input.click();
233
+ };
234
+ const openSlashMenu = () => {
235
+ if (!enableSlashCommand) return;
236
+ const sel = window.getSelection();
237
+ if (!sel || sel.rangeCount === 0) return;
238
+ const range = sel.getRangeAt(0);
239
+ slashStartRangeRef.current = range.cloneRange();
240
+ const rect = range.getBoundingClientRect();
241
+ const editorRect = editorRef.current.getBoundingClientRect();
242
+ setSlashPos({
243
+ top: rect.bottom - editorRect.top + 4,
244
+ left: rect.left - editorRect.left
245
+ });
246
+ setSlashFilter("");
247
+ setSlashIdx(0);
248
+ setSlashOpen(true);
249
+ };
250
+ const closeSlashMenu = () => {
251
+ setSlashOpen(false);
252
+ setSlashFilter("");
253
+ setSlashIdx(0);
254
+ slashStartRangeRef.current = null;
255
+ };
256
+ const filteredSlashItems = React.useMemo(() => {
257
+ if (!slashFilter) return SLASH_ITEMS;
258
+ const f = slashFilter.toLowerCase();
259
+ return SLASH_ITEMS.filter((it) => it.label.toLowerCase().includes(f) || it.key.includes(f));
260
+ }, [slashFilter]);
261
+ const removeSlashTrigger = () => {
262
+ const sel = window.getSelection();
263
+ if (!sel || sel.rangeCount === 0) return;
264
+ const range = sel.getRangeAt(0);
265
+ const node = range.startContainer;
266
+ if (node.nodeType === Node.TEXT_NODE) {
267
+ const text = node.textContent || "";
268
+ const idx = text.lastIndexOf("/");
269
+ if (idx >= 0) {
270
+ node.textContent = text.slice(0, idx) + text.slice(range.startOffset);
271
+ const newRange = document.createRange();
272
+ newRange.setStart(node, idx);
273
+ newRange.collapse(true);
274
+ sel.removeAllRanges();
275
+ sel.addRange(newRange);
276
+ }
277
+ }
278
+ };
279
+ const applySlashItem = (item) => {
280
+ removeSlashTrigger();
281
+ closeSlashMenu();
282
+ switch (item.key) {
283
+ case "paragraph":
284
+ setBlock("p");
285
+ break;
286
+ case "heading":
287
+ setBlock("h2");
288
+ break;
289
+ case "list":
290
+ exec("insertUnorderedList");
291
+ break;
292
+ case "ordered-list":
293
+ exec("insertOrderedList");
294
+ break;
295
+ case "blockquote":
296
+ setBlock("blockquote");
297
+ break;
298
+ case "code-block":
299
+ setBlock("pre");
300
+ break;
301
+ case "divider":
302
+ insertDivider();
303
+ break;
304
+ case "image":
305
+ triggerImageUpload();
306
+ break;
307
+ default:
308
+ break;
309
+ }
310
+ };
311
+ const openLinkEditor = () => {
312
+ saveSelection();
313
+ const sel = window.getSelection();
314
+ if (!sel || sel.rangeCount === 0) return;
315
+ const range = sel.getRangeAt(0);
316
+ const rect = range.getBoundingClientRect();
317
+ const editorRect = editorRef.current.getBoundingClientRect();
318
+ setLinkPos({
319
+ top: rect.bottom - editorRect.top + 4,
320
+ left: rect.left - editorRect.left
321
+ });
322
+ const anchor = sel.anchorNode?.parentElement?.closest("a");
323
+ setLinkValue(anchor?.getAttribute("href") || "");
324
+ setLinkOpen(true);
325
+ };
326
+ const applyLink = () => {
327
+ restoreSelection();
328
+ if (linkValue) {
329
+ exec("createLink", linkValue);
330
+ const sel = window.getSelection();
331
+ const anchor = sel?.anchorNode?.parentElement?.closest("a");
332
+ if (anchor) {
333
+ anchor.setAttribute("target", "_blank");
334
+ anchor.setAttribute("rel", "noopener noreferrer");
335
+ }
336
+ } else {
337
+ exec("unlink");
338
+ }
339
+ setLinkOpen(false);
340
+ };
341
+ const tryMarkdownShortcut = () => {
342
+ if (!enableMarkdownShortcuts) return false;
343
+ const sel = window.getSelection();
344
+ if (!sel || sel.rangeCount === 0) return false;
345
+ const range = sel.getRangeAt(0);
346
+ const node = range.startContainer;
347
+ if (node.nodeType !== Node.TEXT_NODE) return false;
348
+ const text = (node.textContent || "").slice(0, range.startOffset);
349
+ const patterns = [
350
+ [/^###\s$/, () => setBlock("h3")],
351
+ [/^##\s$/, () => setBlock("h2")],
352
+ [/^#\s$/, () => setBlock("h1")],
353
+ [/^-\s$/, () => exec("insertUnorderedList")],
354
+ [/^\*\s$/, () => exec("insertUnorderedList")],
355
+ [/^1\.\s$/, () => exec("insertOrderedList")],
356
+ [/^>\s$/, () => setBlock("blockquote")]
357
+ ];
358
+ for (const [re, fn] of patterns) {
359
+ if (re.test(text)) {
360
+ node.textContent = (node.textContent || "").slice(range.startOffset);
361
+ const newRange = document.createRange();
362
+ newRange.setStart(node, 0);
363
+ newRange.collapse(true);
364
+ sel.removeAllRanges();
365
+ sel.addRange(newRange);
366
+ fn();
367
+ return true;
368
+ }
369
+ }
370
+ return false;
371
+ };
372
+ const handleInput = () => {
373
+ if (composingRef.current) return;
374
+ const el = editorRef.current;
375
+ if (!el) return;
376
+ onChange?.(el.innerHTML);
377
+ updateEmpty();
378
+ updateActiveFormats();
379
+ if (slashOpen) {
380
+ const sel = window.getSelection();
381
+ if (sel && sel.rangeCount > 0) {
382
+ const range = sel.getRangeAt(0);
383
+ const node = range.startContainer;
384
+ if (node.nodeType === Node.TEXT_NODE) {
385
+ const text = node.textContent || "";
386
+ const lastSlash = text.lastIndexOf("/", range.startOffset - 1);
387
+ if (lastSlash >= 0) {
388
+ const filter = text.slice(lastSlash + 1, range.startOffset);
389
+ if (filter.includes(" ") || filter.includes("\n")) {
390
+ closeSlashMenu();
391
+ } else {
392
+ setSlashFilter(filter);
393
+ setSlashIdx(0);
394
+ }
395
+ } else {
396
+ closeSlashMenu();
397
+ }
398
+ }
399
+ }
400
+ }
401
+ };
402
+ const handleKeyDown = (e) => {
403
+ if (readOnly) return;
404
+ if (slashOpen) {
405
+ if (e.key === "ArrowDown") {
406
+ e.preventDefault();
407
+ setSlashIdx((i) => Math.min(i + 1, filteredSlashItems.length - 1));
408
+ return;
409
+ }
410
+ if (e.key === "ArrowUp") {
411
+ e.preventDefault();
412
+ setSlashIdx((i) => Math.max(i - 1, 0));
413
+ return;
414
+ }
415
+ if (e.key === "Enter") {
416
+ e.preventDefault();
417
+ const item = filteredSlashItems[slashIdx];
418
+ if (item) applySlashItem(item);
419
+ return;
420
+ }
421
+ if (e.key === "Escape") {
422
+ e.preventDefault();
423
+ closeSlashMenu();
424
+ return;
425
+ }
426
+ }
427
+ if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
428
+ const k = e.key.toLowerCase();
429
+ if (k === "b") {
430
+ e.preventDefault();
431
+ exec("bold");
432
+ return;
433
+ }
434
+ if (k === "i") {
435
+ e.preventDefault();
436
+ exec("italic");
437
+ return;
438
+ }
439
+ if (k === "u") {
440
+ e.preventDefault();
441
+ exec("underline");
442
+ return;
443
+ }
444
+ if (k === "k") {
445
+ e.preventDefault();
446
+ openLinkEditor();
447
+ return;
448
+ }
449
+ }
450
+ if (enableSlashCommand && e.key === "/") {
451
+ setTimeout(openSlashMenu, 0);
452
+ }
453
+ if (enableMarkdownShortcuts && e.key === " ") {
454
+ if (tryMarkdownShortcut()) {
455
+ e.preventDefault();
456
+ }
457
+ }
458
+ };
459
+ const handlePaste = (e) => {
460
+ if (readOnly) return;
461
+ e.preventDefault();
462
+ const html = e.clipboardData.getData("text/html");
463
+ const text = e.clipboardData.getData("text/plain");
464
+ const clean = html ? sanitizeHtml(html) : escapeHtml(text);
465
+ document.execCommand("insertHTML", false, clean);
466
+ handleInput();
467
+ };
468
+ const handleCompositionStart = () => {
469
+ composingRef.current = true;
470
+ };
471
+ const handleCompositionEnd = () => {
472
+ composingRef.current = false;
473
+ handleInput();
474
+ };
475
+ const handleFocus = () => {
476
+ setIsFocused(true);
477
+ updateActiveFormats();
478
+ };
479
+ const handleBlur = () => {
480
+ setIsFocused(false);
481
+ saveSelection();
482
+ };
483
+ const handleSelectionUpdate = () => {
484
+ updateActiveFormats();
485
+ };
486
+ const onToolbarAction = (item) => {
487
+ switch (item) {
488
+ case "bold":
489
+ exec("bold");
490
+ break;
491
+ case "italic":
492
+ exec("italic");
493
+ break;
494
+ case "underline":
495
+ exec("underline");
496
+ break;
497
+ case "strikethrough":
498
+ exec("strikethrough");
499
+ break;
500
+ case "code": {
501
+ const sel = window.getSelection();
502
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
503
+ const range = sel.getRangeAt(0);
504
+ const code = document.createElement("code");
505
+ try {
506
+ range.surroundContents(code);
507
+ } catch {
508
+ const frag = range.extractContents();
509
+ code.appendChild(frag);
510
+ range.insertNode(code);
511
+ }
512
+ handleInput();
513
+ break;
514
+ }
515
+ case "heading":
516
+ setBlock("h2");
517
+ break;
518
+ case "list":
519
+ exec("insertUnorderedList");
520
+ break;
521
+ case "ordered-list":
522
+ exec("insertOrderedList");
523
+ break;
524
+ case "blockquote":
525
+ setBlock("blockquote");
526
+ break;
527
+ case "code-block":
528
+ setBlock("pre");
529
+ break;
530
+ case "link":
531
+ openLinkEditor();
532
+ break;
533
+ case "image":
534
+ triggerImageUpload();
535
+ break;
536
+ case "divider":
537
+ insertDivider();
538
+ break;
539
+ }
540
+ };
541
+ return /* @__PURE__ */ jsxs("div", { className: clsx_default("lib-xplat-editor", isFocused && "focused", readOnly && "readonly"), children: [
542
+ !readOnly && /* @__PURE__ */ jsx(
543
+ EditorToolbar,
544
+ {
545
+ items: toolbar,
546
+ active: activeFormats,
547
+ onAction: onToolbarAction
548
+ }
549
+ ),
550
+ /* @__PURE__ */ jsxs("div", { className: "lib-xplat-editor__content", style: { minHeight }, children: [
551
+ isEmpty && !isFocused && /* @__PURE__ */ jsx("div", { className: "lib-xplat-editor__placeholder", children: placeholder }),
552
+ /* @__PURE__ */ jsx(
553
+ "div",
554
+ {
555
+ ref: editorRef,
556
+ contentEditable: !readOnly,
557
+ suppressContentEditableWarning: true,
558
+ className: "lib-xplat-editor__editable",
559
+ onInput: handleInput,
560
+ onKeyDown: handleKeyDown,
561
+ onKeyUp: handleSelectionUpdate,
562
+ onMouseUp: handleSelectionUpdate,
563
+ onPaste: handlePaste,
564
+ onCompositionStart: handleCompositionStart,
565
+ onCompositionEnd: handleCompositionEnd,
566
+ onFocus: handleFocus,
567
+ onBlur: handleBlur,
568
+ spellCheck: true
569
+ }
570
+ ),
571
+ slashOpen && filteredSlashItems.length > 0 && /* @__PURE__ */ jsx(
572
+ SlashMenu,
573
+ {
574
+ position: slashPos,
575
+ items: filteredSlashItems,
576
+ activeIndex: slashIdx,
577
+ onSelect: applySlashItem,
578
+ onClose: closeSlashMenu
579
+ }
580
+ ),
581
+ linkOpen && /* @__PURE__ */ jsx(
582
+ LinkPopover,
583
+ {
584
+ position: linkPos,
585
+ value: linkValue,
586
+ onChange: setLinkValue,
587
+ onConfirm: applyLink,
588
+ onClose: () => setLinkOpen(false)
589
+ }
590
+ )
591
+ ] })
592
+ ] });
593
+ };
594
+ Editor.displayName = "Editor";
595
+ var Editor_default = Editor;
596
+ var TOOLBAR_LABEL = {
597
+ bold: "B",
598
+ italic: "I",
599
+ underline: "U",
600
+ strikethrough: "S",
601
+ code: "<>",
602
+ heading: "H",
603
+ list: "\u2022",
604
+ "ordered-list": "1.",
605
+ blockquote: "\u275D",
606
+ "code-block": "[ ]",
607
+ link: "\u{1F517}",
608
+ image: "\u{1F5BC}",
609
+ divider: "\u2014"
610
+ };
611
+ var TOOLBAR_TITLE = {
612
+ bold: "\uAD75\uAC8C (\u2318B)",
613
+ italic: "\uAE30\uC6B8\uC784 (\u2318I)",
614
+ underline: "\uBC11\uC904 (\u2318U)",
615
+ strikethrough: "\uCDE8\uC18C\uC120",
616
+ code: "\uC778\uB77C\uC778 \uCF54\uB4DC",
617
+ heading: "\uC81C\uBAA9",
618
+ list: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D",
619
+ "ordered-list": "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D",
620
+ blockquote: "\uC778\uC6A9",
621
+ "code-block": "\uCF54\uB4DC \uBE14\uB85D",
622
+ link: "\uB9C1\uD06C (\u2318K)",
623
+ image: "\uC774\uBBF8\uC9C0",
624
+ divider: "\uAD6C\uBD84\uC120"
625
+ };
626
+ var isActive = (item, active) => {
627
+ if (item === "heading") return active.has("block:h1") || active.has("block:h2") || active.has("block:h3");
628
+ if (item === "blockquote") return active.has("block:blockquote");
629
+ if (item === "code-block") return active.has("block:pre");
630
+ return active.has(item);
631
+ };
632
+ var EditorToolbar = ({ items, active, onAction }) => {
633
+ return /* @__PURE__ */ jsx("div", { className: "lib-xplat-editor__toolbar", children: items.map((item) => /* @__PURE__ */ jsx(
634
+ "button",
635
+ {
636
+ type: "button",
637
+ className: clsx_default("lib-xplat-editor__toolbar-btn", isActive(item, active) && "active"),
638
+ title: TOOLBAR_TITLE[item],
639
+ onMouseDown: (e) => {
640
+ e.preventDefault();
641
+ onAction(item);
642
+ },
643
+ children: TOOLBAR_LABEL[item]
644
+ },
645
+ item
646
+ )) });
647
+ };
648
+ var SlashMenu = ({ position, items, activeIndex, onSelect, onClose }) => {
649
+ React.useEffect(() => {
650
+ const handleClickOutside = (e) => {
651
+ const target = e.target;
652
+ const menu = document.querySelector(".lib-xplat-editor__slash-menu");
653
+ if (menu && !menu.contains(target)) onClose();
654
+ };
655
+ window.addEventListener("mousedown", handleClickOutside);
656
+ return () => window.removeEventListener("mousedown", handleClickOutside);
657
+ }, [onClose]);
658
+ return /* @__PURE__ */ jsx(
659
+ "div",
660
+ {
661
+ className: "lib-xplat-editor__slash-menu",
662
+ style: { top: position.top, left: position.left },
663
+ children: items.map((item, i) => /* @__PURE__ */ jsxs(
664
+ "button",
665
+ {
666
+ type: "button",
667
+ className: clsx_default("lib-xplat-editor__slash-item", i === activeIndex && "active"),
668
+ onMouseDown: (e) => {
669
+ e.preventDefault();
670
+ onSelect(item);
671
+ },
672
+ children: [
673
+ /* @__PURE__ */ jsx("span", { className: "label", children: item.label }),
674
+ item.hint && /* @__PURE__ */ jsx("span", { className: "hint", children: item.hint })
675
+ ]
676
+ },
677
+ item.key
678
+ ))
679
+ }
680
+ );
681
+ };
682
+ var LinkPopover = ({ position, value, onChange, onConfirm, onClose }) => {
683
+ const inputRef = React.useRef(null);
684
+ React.useEffect(() => {
685
+ inputRef.current?.focus();
686
+ }, []);
687
+ return /* @__PURE__ */ jsxs(
688
+ "div",
689
+ {
690
+ className: "lib-xplat-editor__link-popover",
691
+ style: { top: position.top, left: position.left },
692
+ children: [
693
+ /* @__PURE__ */ jsx(
694
+ "input",
695
+ {
696
+ ref: inputRef,
697
+ type: "url",
698
+ placeholder: "https://",
699
+ value,
700
+ onChange: (e) => onChange(e.target.value),
701
+ onKeyDown: (e) => {
702
+ if (e.key === "Enter") {
703
+ e.preventDefault();
704
+ onConfirm();
705
+ }
706
+ if (e.key === "Escape") {
707
+ e.preventDefault();
708
+ onClose();
709
+ }
710
+ }
711
+ }
712
+ ),
713
+ /* @__PURE__ */ jsx("button", { type: "button", onMouseDown: (e) => {
714
+ e.preventDefault();
715
+ onConfirm();
716
+ }, children: "\uC801\uC6A9" }),
717
+ /* @__PURE__ */ jsx("button", { type: "button", onMouseDown: (e) => {
718
+ e.preventDefault();
719
+ onClose();
720
+ }, children: "\uCDE8\uC18C" })
721
+ ]
722
+ }
723
+ );
724
+ };
725
+ export {
726
+ Editor_default as default
727
+ };