@x-plat/design-system 0.10.0 → 0.12.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.
Files changed (115) hide show
  1. package/dist/components/AutoResizeTextArea/index.cjs +14 -5
  2. package/dist/components/AutoResizeTextArea/index.css +15 -0
  3. package/dist/components/AutoResizeTextArea/index.d.cts +4 -0
  4. package/dist/components/AutoResizeTextArea/index.d.ts +4 -0
  5. package/dist/components/AutoResizeTextArea/index.js +14 -5
  6. package/dist/components/Badge/index.d.cts +1 -1
  7. package/dist/components/Badge/index.d.ts +1 -1
  8. package/dist/components/ChatInput/index.cjs +20 -7
  9. package/dist/components/ChatInput/index.css +15 -0
  10. package/dist/components/ChatInput/index.d.cts +4 -0
  11. package/dist/components/ChatInput/index.d.ts +4 -0
  12. package/dist/components/ChatInput/index.js +20 -7
  13. package/dist/components/CheckBox/index.cjs +8 -3
  14. package/dist/components/CheckBox/index.css +15 -0
  15. package/dist/components/CheckBox/index.d.cts +1 -1
  16. package/dist/components/CheckBox/index.d.ts +1 -1
  17. package/dist/components/CheckBox/index.js +8 -3
  18. package/dist/components/DatePicker/index.cjs +179 -64
  19. package/dist/components/DatePicker/index.css +19 -0
  20. package/dist/components/DatePicker/index.js +179 -64
  21. package/dist/components/Drawer/index.cjs +83 -4
  22. package/dist/components/Drawer/index.js +83 -4
  23. package/dist/components/Dropdown/index.cjs +57 -16
  24. package/dist/components/Dropdown/index.css +3 -0
  25. package/dist/components/Dropdown/index.js +57 -16
  26. package/dist/components/Editor/index.cjs +779 -50
  27. package/dist/components/Editor/index.css +100 -0
  28. package/dist/components/Editor/index.d.cts +1 -1
  29. package/dist/components/Editor/index.d.ts +1 -1
  30. package/dist/components/Editor/index.js +779 -50
  31. package/dist/components/FieldMessage/index.cjs +8 -3
  32. package/dist/components/FieldMessage/index.css +15 -0
  33. package/dist/components/FieldMessage/index.d.cts +11 -4
  34. package/dist/components/FieldMessage/index.d.ts +11 -4
  35. package/dist/components/FieldMessage/index.js +8 -3
  36. package/dist/components/FileUpload/index.cjs +8 -3
  37. package/dist/components/FileUpload/index.css +15 -0
  38. package/dist/components/FileUpload/index.js +8 -3
  39. package/dist/components/ImageSelector/index.cjs +8 -3
  40. package/dist/components/ImageSelector/index.css +15 -0
  41. package/dist/components/ImageSelector/index.js +8 -3
  42. package/dist/components/Input/index.cjs +15 -5
  43. package/dist/components/Input/index.css +15 -0
  44. package/dist/components/Input/index.d.cts +4 -0
  45. package/dist/components/Input/index.d.ts +4 -0
  46. package/dist/components/Input/index.js +15 -5
  47. package/dist/components/Modal/index.cjs +84 -5
  48. package/dist/components/Modal/index.js +84 -5
  49. package/dist/components/NotificationBadge/index.d.cts +1 -1
  50. package/dist/components/NotificationBadge/index.d.ts +1 -1
  51. package/dist/components/Pagination/index.d.cts +1 -1
  52. package/dist/components/Pagination/index.d.ts +1 -1
  53. package/dist/components/PopOver/index.cjs +55 -13
  54. package/dist/components/PopOver/index.css +3 -0
  55. package/dist/components/PopOver/index.js +55 -13
  56. package/dist/components/Progress/index.d.cts +1 -1
  57. package/dist/components/Progress/index.d.ts +1 -1
  58. package/dist/components/Radio/index.cjs +8 -3
  59. package/dist/components/Radio/index.css +15 -0
  60. package/dist/components/Radio/index.d.cts +1 -1
  61. package/dist/components/Radio/index.d.ts +1 -1
  62. package/dist/components/Radio/index.js +8 -3
  63. package/dist/components/Select/index.cjs +120 -39
  64. package/dist/components/Select/index.css +57 -0
  65. package/dist/components/Select/index.d.cts +11 -0
  66. package/dist/components/Select/index.d.ts +11 -0
  67. package/dist/components/Select/index.js +120 -39
  68. package/dist/components/Spinner/index.d.cts +1 -1
  69. package/dist/components/Spinner/index.d.ts +1 -1
  70. package/dist/components/Stat/index.cjs +84 -0
  71. package/dist/components/Stat/index.css +96 -0
  72. package/dist/components/Stat/index.d.cts +37 -0
  73. package/dist/components/Stat/index.d.ts +37 -0
  74. package/dist/components/Stat/index.js +57 -0
  75. package/dist/components/Steps/index.d.cts +1 -1
  76. package/dist/components/Steps/index.d.ts +1 -1
  77. package/dist/components/Switch/index.cjs +14 -5
  78. package/dist/components/Switch/index.css +15 -0
  79. package/dist/components/Switch/index.d.cts +6 -1
  80. package/dist/components/Switch/index.d.ts +6 -1
  81. package/dist/components/Switch/index.js +14 -5
  82. package/dist/components/Tab/index.cjs +49 -25
  83. package/dist/components/Tab/index.css +4 -0
  84. package/dist/components/Tab/index.js +49 -25
  85. package/dist/components/TextArea/index.cjs +14 -4
  86. package/dist/components/TextArea/index.css +15 -0
  87. package/dist/components/TextArea/index.d.cts +4 -0
  88. package/dist/components/TextArea/index.d.ts +4 -0
  89. package/dist/components/TextArea/index.js +14 -4
  90. package/dist/components/TimePicker/index.cjs +461 -0
  91. package/dist/components/TimePicker/index.css +175 -0
  92. package/dist/components/TimePicker/index.d.cts +42 -0
  93. package/dist/components/TimePicker/index.d.ts +42 -0
  94. package/dist/components/TimePicker/index.js +424 -0
  95. package/dist/components/index.cjs +1500 -398
  96. package/dist/components/index.css +385 -0
  97. package/dist/components/index.d.cts +3 -1
  98. package/dist/components/index.d.ts +3 -1
  99. package/dist/components/index.js +1498 -398
  100. package/dist/index.cjs +1524 -422
  101. package/dist/index.css +385 -0
  102. package/dist/index.d.cts +3 -1
  103. package/dist/index.d.ts +3 -1
  104. package/dist/index.js +1522 -422
  105. package/dist/status-njWqQWFV.d.cts +20 -0
  106. package/dist/status-njWqQWFV.d.ts +20 -0
  107. package/guidelines/AGENT_PROMPT.md +3 -3
  108. package/guidelines/Guidelines.md +3 -0
  109. package/guidelines/components/editor.md +141 -0
  110. package/guidelines/components/form.md +19 -0
  111. package/guidelines/components/stat.md +31 -0
  112. package/guidelines/components/time-picker.md +49 -0
  113. package/package.json +1 -1
  114. package/dist/status-g_KP3qT6.d.cts +0 -8
  115. package/dist/status-g_KP3qT6.d.ts +0 -8
@@ -17,6 +17,109 @@ function clsx() {
17
17
  }
18
18
  var clsx_default = clsx;
19
19
 
20
+ // src/components/Editor/highlight.ts
21
+ var CODE_LANGUAGES = [
22
+ { value: "plain", label: "\uC5C6\uC74C" },
23
+ { value: "javascript", label: "JavaScript" },
24
+ { value: "typescript", label: "TypeScript" },
25
+ { value: "json", label: "JSON" },
26
+ { value: "html", label: "HTML" },
27
+ { value: "css", label: "CSS" },
28
+ { value: "python", label: "Python" },
29
+ { value: "sql", label: "SQL" },
30
+ { value: "bash", label: "Bash" }
31
+ ];
32
+ var KEYWORDS = {
33
+ plain: [],
34
+ javascript: "const let var function return if else for while class new this import export from default async await try catch throw typeof instanceof null undefined true false break continue switch case do delete in of yield".split(" "),
35
+ typescript: "const let var function return if else for while class new this import export from default async await try catch throw typeof instanceof null undefined true false break continue switch case do delete in of yield interface type enum implements extends public private protected readonly as satisfies keyof infer never unknown any".split(" "),
36
+ json: ["true", "false", "null"],
37
+ html: [],
38
+ css: [],
39
+ python: "def class return if elif else for while import from as try except finally raise with lambda None True False and or not in is pass break continue global nonlocal yield async await".split(" "),
40
+ sql: "SELECT FROM WHERE INSERT INTO VALUES UPDATE SET DELETE CREATE TABLE ALTER DROP JOIN LEFT RIGHT INNER OUTER ON GROUP BY ORDER HAVING LIMIT OFFSET AS AND OR NOT NULL DISTINCT UNION INDEX PRIMARY KEY FOREIGN REFERENCES DEFAULT".split(" "),
41
+ bash: "if then else fi for while do done case esac function return export local echo cd ls rm cp mv mkdir sudo source set unset exit".split(" ")
42
+ };
43
+ var LINE_COMMENT = {
44
+ javascript: "//",
45
+ typescript: "//",
46
+ css: "",
47
+ python: "#",
48
+ sql: "--",
49
+ bash: "#"
50
+ };
51
+ var escape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
52
+ var wrap = (cls, text) => `<span class="tok-${cls}">${escape(text)}</span>`;
53
+ var highlight = (code, lang) => {
54
+ if (lang === "plain" || !code) return escape(code);
55
+ const keywords = new Set(KEYWORDS[lang]);
56
+ const lineComment = LINE_COMMENT[lang];
57
+ const caseInsensitive = lang === "sql";
58
+ let out = "";
59
+ let i = 0;
60
+ while (i < code.length) {
61
+ const rest = code.slice(i);
62
+ if ((lang === "javascript" || lang === "typescript" || lang === "css") && rest.startsWith("/*")) {
63
+ const end = code.indexOf("*/", i + 2);
64
+ const stop = end === -1 ? code.length : end + 2;
65
+ out += wrap("comment", code.slice(i, stop));
66
+ i = stop;
67
+ continue;
68
+ }
69
+ if (lineComment && rest.startsWith(lineComment)) {
70
+ const nl = code.indexOf("\n", i);
71
+ const stop = nl === -1 ? code.length : nl;
72
+ out += wrap("comment", code.slice(i, stop));
73
+ i = stop;
74
+ continue;
75
+ }
76
+ const q = rest[0];
77
+ if (q === '"' || q === "'" || q === "`") {
78
+ let j = 1;
79
+ while (j < rest.length) {
80
+ if (rest[j] === "\\") {
81
+ j += 2;
82
+ continue;
83
+ }
84
+ if (rest[j] === q) {
85
+ j += 1;
86
+ break;
87
+ }
88
+ if (rest[j] === "\n" && q !== "`") break;
89
+ j += 1;
90
+ }
91
+ out += wrap("string", rest.slice(0, j));
92
+ i += j;
93
+ continue;
94
+ }
95
+ const num = /^\d+(\.\d+)?/.exec(rest);
96
+ if (num && !/[\w$]/.test(code[i - 1] ?? "")) {
97
+ out += wrap("number", num[0]);
98
+ i += num[0].length;
99
+ continue;
100
+ }
101
+ const word = /^[A-Za-z_$][\w$]*/.exec(rest);
102
+ if (word) {
103
+ const w = word[0];
104
+ const hit = caseInsensitive ? keywords.has(w.toUpperCase()) : keywords.has(w);
105
+ out += hit ? wrap("keyword", w) : escape(w);
106
+ i += w.length;
107
+ continue;
108
+ }
109
+ if (lang === "html" && rest.startsWith("<")) {
110
+ const tag = /^<\/?[A-Za-z][\w-]*/.exec(rest);
111
+ if (tag) {
112
+ out += wrap("keyword", tag[0]);
113
+ i += tag[0].length;
114
+ continue;
115
+ }
116
+ }
117
+ out += escape(code[i]);
118
+ i += 1;
119
+ }
120
+ return out;
121
+ };
122
+
20
123
  // src/components/Editor/Editor.tsx
21
124
  import { jsx, jsxs } from "react/jsx-runtime";
22
125
  var DEFAULT_TOOLBAR = [
@@ -25,14 +128,27 @@ var DEFAULT_TOOLBAR = [
25
128
  "underline",
26
129
  "strikethrough",
27
130
  "code",
28
- "heading",
131
+ "heading-1",
132
+ "heading-2",
133
+ "heading-3",
29
134
  "list",
30
135
  "ordered-list",
31
136
  "blockquote",
32
137
  "code-block",
33
138
  "link",
34
139
  "image",
35
- "divider"
140
+ "divider",
141
+ "checklist",
142
+ "table",
143
+ "align-left",
144
+ "align-center",
145
+ "align-right",
146
+ "highlight",
147
+ "superscript",
148
+ "subscript",
149
+ "clear-format",
150
+ "undo",
151
+ "redo"
36
152
  ];
37
153
  var ALLOWED_TAGS = /* @__PURE__ */ new Set([
38
154
  "p",
@@ -61,12 +177,51 @@ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
61
177
  "img",
62
178
  "hr",
63
179
  "span",
64
- "div"
180
+ "div",
181
+ "sub",
182
+ "sup",
183
+ "mark",
184
+ "table",
185
+ "thead",
186
+ "tbody",
187
+ "tr",
188
+ "th",
189
+ "td"
65
190
  ]);
66
191
  var ALLOWED_ATTRS = {
67
192
  a: ["href", "target", "rel"],
68
- img: ["src", "alt"]
193
+ img: ["src", "alt", "width"],
194
+ // 정렬·체크리스트는 클래스로만 표현한다. style 을 열면 붙여넣기로 아무 CSS 나 들어온다
195
+ p: ["class"],
196
+ h1: ["class"],
197
+ h2: ["class"],
198
+ h3: ["class"],
199
+ h4: ["class"],
200
+ h5: ["class"],
201
+ h6: ["class"],
202
+ ul: ["class"],
203
+ ol: ["class"],
204
+ li: ["class", "data-checked"],
205
+ blockquote: ["class"],
206
+ div: ["class"],
207
+ span: ["class"],
208
+ pre: ["class"],
209
+ code: ["class"],
210
+ mark: ["class"],
211
+ table: ["class"],
212
+ td: ["class"],
213
+ th: ["class"],
214
+ tr: ["class"]
69
215
  };
216
+ var DROP_WITH_CONTENT = /* @__PURE__ */ new Set([
217
+ "script",
218
+ "style",
219
+ "noscript",
220
+ "iframe",
221
+ "object",
222
+ "embed",
223
+ "template"
224
+ ]);
70
225
  var sanitizeHtml = (input) => {
71
226
  const doc = new DOMParser().parseFromString(`<div>${input}</div>`, "text/html");
72
227
  const root = doc.body.firstElementChild;
@@ -75,6 +230,10 @@ var sanitizeHtml = (input) => {
75
230
  const children = Array.from(node.children);
76
231
  for (const child of children) {
77
232
  const tag = child.tagName.toLowerCase();
233
+ if (DROP_WITH_CONTENT.has(tag)) {
234
+ node.removeChild(child);
235
+ continue;
236
+ }
78
237
  if (!ALLOWED_TAGS.has(tag)) {
79
238
  while (child.firstChild) node.insertBefore(child.firstChild, child);
80
239
  node.removeChild(child);
@@ -105,7 +264,9 @@ var escapeHtml = (text) => {
105
264
  };
106
265
  var SLASH_ITEMS = [
107
266
  { key: "paragraph", label: "\uB2E8\uB77D", hint: "\uAE30\uBCF8 \uD14D\uC2A4\uD2B8" },
108
- { key: "heading", label: "\uC81C\uBAA9 1", hint: "\uD070 \uC81C\uBAA9" },
267
+ { key: "heading-1", label: "\uC81C\uBAA9 1", hint: "\uAC00\uC7A5 \uD070 \uC81C\uBAA9" },
268
+ { key: "heading-2", label: "\uC81C\uBAA9 2", hint: "" },
269
+ { key: "heading-3", label: "\uC81C\uBAA9 3", hint: "" },
109
270
  { key: "list", label: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D", hint: "\u2022 \uD56D\uBAA9" },
110
271
  { key: "ordered-list", label: "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D", hint: "1. \uD56D\uBAA9" },
111
272
  { key: "blockquote", label: "\uC778\uC6A9", hint: "" },
@@ -139,12 +300,18 @@ var Editor = (props) => {
139
300
  const [linkOpen, setLinkOpen] = React.useState(false);
140
301
  const [linkValue, setLinkValue] = React.useState("");
141
302
  const [linkPos, setLinkPos] = React.useState({ top: 0, left: 0 });
303
+ const lastEmittedRef = React.useRef(null);
142
304
  React.useEffect(() => {
143
- if (!editorRef.current) return;
144
- if (editorRef.current.innerHTML !== value) {
145
- editorRef.current.innerHTML = sanitizeHtml(value || "");
146
- updateEmpty();
147
- }
305
+ const el = editorRef.current;
306
+ if (!el) return;
307
+ if (value === lastEmittedRef.current) return;
308
+ if (el.innerHTML === value) return;
309
+ el.innerHTML = sanitizeHtml(value || "");
310
+ if (!el.firstChild) el.innerHTML = "<p><br></p>";
311
+ lastEmittedRef.current = null;
312
+ historyRef.current = [{ html: el.innerHTML, caret: 0 }];
313
+ historyIdxRef.current = 0;
314
+ updateEmpty();
148
315
  }, [value]);
149
316
  const updateEmpty = () => {
150
317
  const el = editorRef.current;
@@ -160,10 +327,19 @@ var Editor = (props) => {
160
327
  }
161
328
  };
162
329
  const restoreSelection = () => {
163
- if (!lastRangeRef.current) return;
330
+ const range = lastRangeRef.current;
331
+ if (!range) return;
332
+ if (!editorRef.current?.contains(range.startContainer)) {
333
+ lastRangeRef.current = null;
334
+ return;
335
+ }
164
336
  const sel = window.getSelection();
165
- sel?.removeAllRanges();
166
- sel?.addRange(lastRangeRef.current);
337
+ try {
338
+ sel?.removeAllRanges();
339
+ sel?.addRange(range);
340
+ } catch {
341
+ lastRangeRef.current = null;
342
+ }
167
343
  };
168
344
  const updateActiveFormats = () => {
169
345
  if (!editorRef.current) return;
@@ -179,6 +355,17 @@ var Editor = (props) => {
179
355
  if (document.queryCommandState("insertOrderedList")) next.add("ordered-list");
180
356
  const block = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
181
357
  if (block) next.add(`block:${block}`);
358
+ if (document.queryCommandState("superscript")) next.add("superscript");
359
+ if (document.queryCommandState("subscript")) next.add("subscript");
360
+ } catch {
361
+ }
362
+ try {
363
+ const blockEl = currentBlock();
364
+ if (blockEl?.classList.contains("align-center")) next.add("align:center");
365
+ else if (blockEl?.classList.contains("align-right")) next.add("align:right");
366
+ else if (blockEl) next.add("align:left");
367
+ if (blockEl?.closest("mark")) next.add("highlight");
368
+ if (blockEl?.closest("ul.checklist")) next.add("checklist");
182
369
  } catch {
183
370
  }
184
371
  setActiveFormats(next);
@@ -192,7 +379,374 @@ var Editor = (props) => {
192
379
  const setBlock = (tag) => {
193
380
  exec("formatBlock", tag);
194
381
  };
382
+ const toggleBlock = (tag) => {
383
+ const current = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
384
+ setBlock(current === tag ? "p" : tag);
385
+ };
386
+ const historyRef = React.useRef([]);
387
+ const historyIdxRef = React.useRef(-1);
388
+ const lastPushAtRef = React.useRef(0);
389
+ const isRestoringRef = React.useRef(false);
390
+ const caretOffset = (rootEl) => {
391
+ const el = rootEl ?? editorRef.current;
392
+ const sel = window.getSelection();
393
+ if (!el || !sel || sel.rangeCount === 0 || !el.contains(sel.anchorNode)) return 0;
394
+ const range = sel.getRangeAt(0).cloneRange();
395
+ range.selectNodeContents(el);
396
+ range.setEnd(sel.getRangeAt(0).endContainer, sel.getRangeAt(0).endOffset);
397
+ return range.toString().length;
398
+ };
399
+ const setCaretOffset = (offset, rootEl) => {
400
+ const el = rootEl ?? editorRef.current;
401
+ if (!el) return;
402
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
403
+ let remaining = offset;
404
+ let node = null;
405
+ while (node = walker.nextNode()) {
406
+ const len = node.textContent?.length ?? 0;
407
+ if (remaining <= len) {
408
+ const range = document.createRange();
409
+ range.setStart(node, remaining);
410
+ range.collapse(true);
411
+ const sel = window.getSelection();
412
+ sel?.removeAllRanges();
413
+ sel?.addRange(range);
414
+ return;
415
+ }
416
+ remaining -= len;
417
+ }
418
+ placeCaretAtEnd(el);
419
+ };
420
+ const pushHistory = (force = false) => {
421
+ const el = editorRef.current;
422
+ if (!el || isRestoringRef.current) return;
423
+ const html = el.innerHTML;
424
+ const hist = historyRef.current;
425
+ const cur = hist[historyIdxRef.current];
426
+ if (cur && cur.html === html) return;
427
+ const now = Date.now();
428
+ const coalesce = !force && cur && now - lastPushAtRef.current < 500;
429
+ lastPushAtRef.current = now;
430
+ if (coalesce) {
431
+ hist[historyIdxRef.current] = { html, caret: caretOffset() };
432
+ return;
433
+ }
434
+ hist.length = historyIdxRef.current + 1;
435
+ hist.push({ html, caret: caretOffset() });
436
+ if (hist.length > 200) hist.shift();
437
+ historyIdxRef.current = hist.length - 1;
438
+ };
439
+ const applySnapshot = (snap) => {
440
+ const el = editorRef.current;
441
+ if (!el) return;
442
+ isRestoringRef.current = true;
443
+ el.innerHTML = snap.html;
444
+ setCaretOffset(snap.caret);
445
+ isRestoringRef.current = false;
446
+ lastEmittedRef.current = el.innerHTML;
447
+ onChange?.(el.innerHTML);
448
+ updateEmpty();
449
+ updateActiveFormats();
450
+ };
451
+ const undo = () => {
452
+ pushHistory(true);
453
+ if (historyIdxRef.current <= 0) return;
454
+ historyIdxRef.current -= 1;
455
+ applySnapshot(historyRef.current[historyIdxRef.current]);
456
+ };
457
+ const redo = () => {
458
+ if (historyIdxRef.current >= historyRef.current.length - 1) return;
459
+ historyIdxRef.current += 1;
460
+ applySnapshot(historyRef.current[historyIdxRef.current]);
461
+ };
462
+ const langOf = (pre) => {
463
+ const cls = Array.from(pre.classList).find((c) => c.startsWith("lang-"));
464
+ return cls?.slice(5) ?? "plain";
465
+ };
466
+ const [codeLang, setCodeLang] = React.useState("plain");
467
+ const highlightRafRef = React.useRef(0);
468
+ const rehighlight = (pre) => {
469
+ if (composingRef.current) return;
470
+ const code = pre.querySelector("code");
471
+ if (!code) return;
472
+ const lang = langOf(pre);
473
+ const text = code.textContent || "";
474
+ const next = highlight(text, lang);
475
+ if (code.innerHTML === next) return;
476
+ const caret = caretOffset(code);
477
+ isRestoringRef.current = true;
478
+ code.innerHTML = next;
479
+ setCaretOffset(caret, code);
480
+ isRestoringRef.current = false;
481
+ };
482
+ const scheduleHighlight = () => {
483
+ const pre = closestPre();
484
+ if (!pre) return;
485
+ cancelAnimationFrame(highlightRafRef.current);
486
+ highlightRafRef.current = requestAnimationFrame(() => rehighlight(pre));
487
+ };
488
+ const changeCodeLang = (lang) => {
489
+ const pre = closestPre();
490
+ if (!pre) return;
491
+ pushHistory(true);
492
+ Array.from(pre.classList).filter((c) => c.startsWith("lang-")).forEach((c) => pre.classList.remove(c));
493
+ if (lang !== "plain") pre.classList.add(`lang-${lang}`);
494
+ setCodeLang(lang);
495
+ rehighlight(pre);
496
+ handleInput();
497
+ };
498
+ const closestPre = () => {
499
+ const sel = window.getSelection();
500
+ const node = sel?.anchorNode;
501
+ if (!node || !editorRef.current?.contains(node)) return null;
502
+ const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
503
+ return el?.closest("pre") ?? null;
504
+ };
505
+ const insertCodeBlock = () => {
506
+ pushHistory(true);
507
+ const el = editorRef.current;
508
+ const sel = window.getSelection();
509
+ if (!el || !sel || sel.rangeCount === 0) return;
510
+ const existing = closestPre();
511
+ if (existing) {
512
+ const p = document.createElement("p");
513
+ p.textContent = existing.textContent || "";
514
+ if (!p.textContent) p.innerHTML = "<br>";
515
+ existing.replaceWith(p);
516
+ placeCaretAtEnd(p);
517
+ handleInput();
518
+ return;
519
+ }
520
+ const range = sel.getRangeAt(0);
521
+ const frag = range.cloneContents();
522
+ const holder = document.createElement("div");
523
+ holder.appendChild(frag);
524
+ const BLOCK = /^(p|div|h[1-6]|li|blockquote|pre|tr)$/i;
525
+ const linesOf = (node) => {
526
+ const out = [];
527
+ let cur = "";
528
+ for (const child of Array.from(node.childNodes)) {
529
+ if (child.nodeType === Node.TEXT_NODE) {
530
+ cur += child.textContent || "";
531
+ } else if (child.nodeName === "BR") {
532
+ out.push(cur);
533
+ cur = "";
534
+ } else if (BLOCK.test(child.nodeName)) {
535
+ if (cur) {
536
+ out.push(cur);
537
+ cur = "";
538
+ }
539
+ const sub = linesOf(child);
540
+ out.push(...sub.length > 0 ? sub : [""]);
541
+ } else {
542
+ cur += linesOf(child).join("\n");
543
+ }
544
+ }
545
+ if (cur) out.push(cur);
546
+ return out;
547
+ };
548
+ const text = linesOf(holder).join("\n").replace(/\n+$/, "");
549
+ const pre = document.createElement("pre");
550
+ const code = document.createElement("code");
551
+ code.textContent = text || "";
552
+ pre.appendChild(code);
553
+ range.deleteContents();
554
+ range.insertNode(pre);
555
+ ensureParagraphAfter(pre);
556
+ placeCaretAtEnd(code);
557
+ handleInput();
558
+ };
559
+ const insertTextAtCaret = (text) => {
560
+ const sel = window.getSelection();
561
+ if (!sel || sel.rangeCount === 0) return;
562
+ const range = sel.getRangeAt(0);
563
+ range.deleteContents();
564
+ const node = document.createTextNode(text);
565
+ range.insertNode(node);
566
+ range.setStartAfter(node);
567
+ range.collapse(true);
568
+ sel.removeAllRanges();
569
+ sel.addRange(range);
570
+ handleInput();
571
+ };
572
+ const ensureParagraphAfter = (node) => {
573
+ const next = node.nextElementSibling;
574
+ if (next && next.tagName === "P") return;
575
+ const p = document.createElement("p");
576
+ p.innerHTML = "<br>";
577
+ node.after(p);
578
+ };
579
+ const placeCaretAtEnd = (node) => {
580
+ const sel = window.getSelection();
581
+ if (!sel) return;
582
+ const r2 = document.createRange();
583
+ r2.selectNodeContents(node);
584
+ r2.collapse(false);
585
+ sel.removeAllRanges();
586
+ sel.addRange(r2);
587
+ editorRef.current?.focus();
588
+ };
589
+ const clearFormat = () => {
590
+ exec("removeFormat");
591
+ exec("unlink");
592
+ setBlock("p");
593
+ };
594
+ const indentList = (outdent) => {
595
+ exec(outdent ? "outdent" : "indent");
596
+ };
597
+ const inListItem = () => {
598
+ const sel = window.getSelection();
599
+ const node = sel?.anchorNode;
600
+ if (!node || !editorRef.current?.contains(node)) return false;
601
+ const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
602
+ return !!el?.closest("li");
603
+ };
604
+ const currentBlock = () => {
605
+ const sel = window.getSelection();
606
+ const node = sel?.anchorNode;
607
+ const root = editorRef.current;
608
+ if (!node || !root?.contains(node)) return null;
609
+ let el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
610
+ while (el && el !== root && !/^(P|H[1-6]|LI|BLOCKQUOTE|PRE|DIV|TD|TH)$/.test(el.tagName)) {
611
+ el = el.parentElement;
612
+ }
613
+ return el && el !== root ? el : null;
614
+ };
615
+ const setAlign = (align) => {
616
+ const block = currentBlock();
617
+ if (!block) return;
618
+ pushHistory(true);
619
+ block.classList.remove("align-left", "align-center", "align-right");
620
+ if (align !== "left") block.classList.add(`align-${align}`);
621
+ handleInput();
622
+ };
623
+ const toggleChecklist = () => {
624
+ pushHistory(true);
625
+ const block = currentBlock();
626
+ const list = block?.closest("ul");
627
+ if (list?.classList.contains("checklist")) {
628
+ list.classList.remove("checklist");
629
+ list.querySelectorAll("li").forEach((li) => li.removeAttribute("data-checked"));
630
+ handleInput();
631
+ return;
632
+ }
633
+ if (list) {
634
+ list.classList.add("checklist");
635
+ list.querySelectorAll("li").forEach((li) => li.setAttribute("data-checked", "false"));
636
+ handleInput();
637
+ return;
638
+ }
639
+ exec("insertUnorderedList");
640
+ const made = currentBlock()?.closest("ul");
641
+ if (made) {
642
+ made.classList.add("checklist");
643
+ made.querySelectorAll("li").forEach((li) => li.setAttribute("data-checked", "false"));
644
+ }
645
+ handleInput();
646
+ };
647
+ const handleEditorClick = (e) => {
648
+ if (readOnly) return;
649
+ const target = e.target;
650
+ const li = target.closest?.("li");
651
+ if (!li || !li.closest("ul.checklist")) return;
652
+ const rect = li.getBoundingClientRect();
653
+ if (e.clientX - rect.left > 20) return;
654
+ e.preventDefault();
655
+ pushHistory(true);
656
+ li.setAttribute("data-checked", li.getAttribute("data-checked") === "true" ? "false" : "true");
657
+ handleInput();
658
+ };
659
+ const toggleHighlight = () => {
660
+ const sel = window.getSelection();
661
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
662
+ pushHistory(true);
663
+ const range = sel.getRangeAt(0);
664
+ const node = sel.anchorNode;
665
+ const el = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement;
666
+ const existing = el?.closest("mark");
667
+ if (existing) {
668
+ const parent = existing.parentNode;
669
+ while (existing.firstChild) parent?.insertBefore(existing.firstChild, existing);
670
+ parent?.removeChild(existing);
671
+ handleInput();
672
+ return;
673
+ }
674
+ const mark = document.createElement("mark");
675
+ try {
676
+ range.surroundContents(mark);
677
+ } catch {
678
+ const frag = range.extractContents();
679
+ mark.appendChild(frag);
680
+ range.insertNode(mark);
681
+ }
682
+ handleInput();
683
+ };
684
+ const insertTable = (cols = 3, rows = 2) => {
685
+ const sel = window.getSelection();
686
+ if (!sel || sel.rangeCount === 0) return;
687
+ pushHistory(true);
688
+ const table = document.createElement("table");
689
+ const thead = document.createElement("thead");
690
+ const headRow = document.createElement("tr");
691
+ for (let c = 0; c < cols; c++) {
692
+ const th = document.createElement("th");
693
+ th.innerHTML = "<br>";
694
+ headRow.appendChild(th);
695
+ }
696
+ thead.appendChild(headRow);
697
+ table.appendChild(thead);
698
+ const tbody = document.createElement("tbody");
699
+ for (let r2 = 0; r2 < rows; r2++) {
700
+ const tr = document.createElement("tr");
701
+ for (let c = 0; c < cols; c++) {
702
+ const td = document.createElement("td");
703
+ td.innerHTML = "<br>";
704
+ tr.appendChild(td);
705
+ }
706
+ tbody.appendChild(tr);
707
+ }
708
+ table.appendChild(tbody);
709
+ const range = sel.getRangeAt(0);
710
+ range.deleteContents();
711
+ range.insertNode(table);
712
+ ensureParagraphAfter(table);
713
+ const first = table.querySelector("th");
714
+ if (first) placeCaretAtEnd(first);
715
+ handleInput();
716
+ };
717
+ const moveTableCell = (backwards) => {
718
+ const block = currentBlock();
719
+ const cell = block?.closest("td, th");
720
+ if (!cell) return false;
721
+ const table = cell.closest("table");
722
+ if (!table) return false;
723
+ const cells = Array.from(table.querySelectorAll("td, th"));
724
+ const i = cells.indexOf(cell);
725
+ const next = cells[i + (backwards ? -1 : 1)];
726
+ if (next) {
727
+ placeCaretAtEnd(next);
728
+ return true;
729
+ }
730
+ if (!backwards) {
731
+ pushHistory(true);
732
+ const lastRow = table.querySelector("tbody")?.lastElementChild;
733
+ if (lastRow) {
734
+ const tr = document.createElement("tr");
735
+ for (let c = 0; c < lastRow.children.length; c++) {
736
+ const td = document.createElement("td");
737
+ td.innerHTML = "<br>";
738
+ tr.appendChild(td);
739
+ }
740
+ lastRow.after(tr);
741
+ placeCaretAtEnd(tr.firstElementChild);
742
+ handleInput();
743
+ return true;
744
+ }
745
+ }
746
+ return false;
747
+ };
195
748
  const insertDivider = () => {
749
+ pushHistory(true);
196
750
  exec("insertHorizontalRule");
197
751
  const el = editorRef.current;
198
752
  if (el && el.lastElementChild?.tagName === "HR") {
@@ -238,7 +792,9 @@ var Editor = (props) => {
238
792
  const range = sel.getRangeAt(0);
239
793
  slashStartRangeRef.current = range.cloneRange();
240
794
  const rect = range.getBoundingClientRect();
241
- const editorRect = editorRef.current.getBoundingClientRect();
795
+ const editorEl = editorRef.current;
796
+ if (!editorEl) return;
797
+ const editorRect = editorEl.getBoundingClientRect();
242
798
  setSlashPos({
243
799
  top: rect.bottom - editorRect.top + 4,
244
800
  left: rect.left - editorRect.left
@@ -283,8 +839,14 @@ var Editor = (props) => {
283
839
  case "paragraph":
284
840
  setBlock("p");
285
841
  break;
286
- case "heading":
287
- setBlock("h2");
842
+ case "heading-1":
843
+ toggleBlock("h1");
844
+ break;
845
+ case "heading-2":
846
+ toggleBlock("h2");
847
+ break;
848
+ case "heading-3":
849
+ toggleBlock("h3");
288
850
  break;
289
851
  case "list":
290
852
  exec("insertUnorderedList");
@@ -296,7 +858,7 @@ var Editor = (props) => {
296
858
  setBlock("blockquote");
297
859
  break;
298
860
  case "code-block":
299
- setBlock("pre");
861
+ insertCodeBlock();
300
862
  break;
301
863
  case "divider":
302
864
  insertDivider();
@@ -314,7 +876,9 @@ var Editor = (props) => {
314
876
  if (!sel || sel.rangeCount === 0) return;
315
877
  const range = sel.getRangeAt(0);
316
878
  const rect = range.getBoundingClientRect();
317
- const editorRect = editorRef.current.getBoundingClientRect();
879
+ const editorEl2 = editorRef.current;
880
+ if (!editorEl2) return;
881
+ const editorRect = editorEl2.getBoundingClientRect();
318
882
  setLinkPos({
319
883
  top: rect.bottom - editorRect.top + 4,
320
884
  left: rect.left - editorRect.left
@@ -347,13 +911,13 @@ var Editor = (props) => {
347
911
  if (node.nodeType !== Node.TEXT_NODE) return false;
348
912
  const text = (node.textContent || "").slice(0, range.startOffset);
349
913
  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")]
914
+ [/^###$/, () => setBlock("h3")],
915
+ [/^##$/, () => setBlock("h2")],
916
+ [/^#$/, () => setBlock("h1")],
917
+ [/^-$/, () => exec("insertUnorderedList")],
918
+ [/^\*$/, () => exec("insertUnorderedList")],
919
+ [/^1\.$/, () => exec("insertOrderedList")],
920
+ [/^>$/, () => setBlock("blockquote")]
357
921
  ];
358
922
  for (const [re, fn] of patterns) {
359
923
  if (re.test(text)) {
@@ -373,7 +937,11 @@ var Editor = (props) => {
373
937
  if (composingRef.current) return;
374
938
  const el = editorRef.current;
375
939
  if (!el) return;
376
- onChange?.(el.innerHTML);
940
+ const html = el.innerHTML;
941
+ lastEmittedRef.current = html;
942
+ pushHistory();
943
+ scheduleHighlight();
944
+ onChange?.(html);
377
945
  updateEmpty();
378
946
  updateActiveFormats();
379
947
  if (slashOpen) {
@@ -401,6 +969,40 @@ var Editor = (props) => {
401
969
  };
402
970
  const handleKeyDown = (e) => {
403
971
  if (readOnly) return;
972
+ if (composingRef.current || e.nativeEvent.isComposing || e.keyCode === 229) return;
973
+ const pre = closestPre();
974
+ if (pre && !slashOpen) {
975
+ if (e.key === "Enter" && !e.shiftKey) {
976
+ const text = pre.textContent || "";
977
+ if (text.endsWith("\n\n")) {
978
+ e.preventDefault();
979
+ const code = pre.querySelector("code") ?? pre;
980
+ code.textContent = text.replace(/\n\n$/, "");
981
+ ensureParagraphAfter(pre);
982
+ const next = pre.nextElementSibling;
983
+ if (next) placeCaretAtEnd(next);
984
+ handleInput();
985
+ return;
986
+ }
987
+ e.preventDefault();
988
+ insertTextAtCaret("\n");
989
+ return;
990
+ }
991
+ if (e.key === "Tab") {
992
+ e.preventDefault();
993
+ insertTextAtCaret(" ");
994
+ return;
995
+ }
996
+ }
997
+ if (e.key === "Tab" && moveTableCell(e.shiftKey)) {
998
+ e.preventDefault();
999
+ return;
1000
+ }
1001
+ if (e.key === "Tab" && inListItem()) {
1002
+ e.preventDefault();
1003
+ indentList(e.shiftKey);
1004
+ return;
1005
+ }
404
1006
  if (slashOpen) {
405
1007
  if (e.key === "ArrowDown") {
406
1008
  e.preventDefault();
@@ -424,6 +1026,21 @@ var Editor = (props) => {
424
1026
  return;
425
1027
  }
426
1028
  }
1029
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "z") {
1030
+ e.preventDefault();
1031
+ if (e.shiftKey) redo();
1032
+ else undo();
1033
+ return;
1034
+ }
1035
+ if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "v") {
1036
+ plainPasteRef.current = true;
1037
+ return;
1038
+ }
1039
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "y") {
1040
+ e.preventDefault();
1041
+ redo();
1042
+ return;
1043
+ }
427
1044
  if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
428
1045
  const k = e.key.toLowerCase();
429
1046
  if (k === "b") {
@@ -447,6 +1064,7 @@ var Editor = (props) => {
447
1064
  return;
448
1065
  }
449
1066
  }
1067
+ if (pre) return;
450
1068
  if (enableSlashCommand && e.key === "/") {
451
1069
  setTimeout(openSlashMenu, 0);
452
1070
  }
@@ -456,11 +1074,21 @@ var Editor = (props) => {
456
1074
  }
457
1075
  }
458
1076
  };
1077
+ const plainPasteRef = React.useRef(false);
459
1078
  const handlePaste = (e) => {
460
1079
  if (readOnly) return;
461
1080
  e.preventDefault();
462
- const html = e.clipboardData.getData("text/html");
463
1081
  const text = e.clipboardData.getData("text/plain");
1082
+ if (plainPasteRef.current) {
1083
+ plainPasteRef.current = false;
1084
+ insertTextAtCaret(text);
1085
+ return;
1086
+ }
1087
+ if (closestPre()) {
1088
+ insertTextAtCaret(text);
1089
+ return;
1090
+ }
1091
+ const html = e.clipboardData.getData("text/html");
464
1092
  const clean = html ? sanitizeHtml(html) : escapeHtml(text);
465
1093
  document.execCommand("insertHTML", false, clean);
466
1094
  handleInput();
@@ -482,8 +1110,11 @@ var Editor = (props) => {
482
1110
  };
483
1111
  const handleSelectionUpdate = () => {
484
1112
  updateActiveFormats();
1113
+ const pre = closestPre();
1114
+ setCodeLang(pre ? langOf(pre) : "plain");
485
1115
  };
486
1116
  const onToolbarAction = (item) => {
1117
+ if (closestPre() && !["code-block", "undo", "redo"].includes(item)) return;
487
1118
  switch (item) {
488
1119
  case "bold":
489
1120
  exec("bold");
@@ -512,8 +1143,14 @@ var Editor = (props) => {
512
1143
  handleInput();
513
1144
  break;
514
1145
  }
515
- case "heading":
516
- setBlock("h2");
1146
+ case "heading-1":
1147
+ toggleBlock("h1");
1148
+ break;
1149
+ case "heading-2":
1150
+ toggleBlock("h2");
1151
+ break;
1152
+ case "heading-3":
1153
+ toggleBlock("h3");
517
1154
  break;
518
1155
  case "list":
519
1156
  exec("insertUnorderedList");
@@ -525,7 +1162,7 @@ var Editor = (props) => {
525
1162
  setBlock("blockquote");
526
1163
  break;
527
1164
  case "code-block":
528
- setBlock("pre");
1165
+ insertCodeBlock();
529
1166
  break;
530
1167
  case "link":
531
1168
  openLinkEditor();
@@ -536,6 +1173,39 @@ var Editor = (props) => {
536
1173
  case "divider":
537
1174
  insertDivider();
538
1175
  break;
1176
+ case "checklist":
1177
+ toggleChecklist();
1178
+ break;
1179
+ case "align-left":
1180
+ setAlign("left");
1181
+ break;
1182
+ case "align-center":
1183
+ setAlign("center");
1184
+ break;
1185
+ case "align-right":
1186
+ setAlign("right");
1187
+ break;
1188
+ case "highlight":
1189
+ toggleHighlight();
1190
+ break;
1191
+ case "superscript":
1192
+ exec("superscript");
1193
+ break;
1194
+ case "subscript":
1195
+ exec("subscript");
1196
+ break;
1197
+ case "table":
1198
+ insertTable();
1199
+ break;
1200
+ case "clear-format":
1201
+ clearFormat();
1202
+ break;
1203
+ case "undo":
1204
+ undo();
1205
+ break;
1206
+ case "redo":
1207
+ redo();
1208
+ break;
539
1209
  }
540
1210
  };
541
1211
  return /* @__PURE__ */ jsxs("div", { className: clsx_default("lib-xplat-editor", isFocused && "focused", readOnly && "readonly"), children: [
@@ -544,7 +1214,10 @@ var Editor = (props) => {
544
1214
  {
545
1215
  items: toolbar,
546
1216
  active: activeFormats,
547
- onAction: onToolbarAction
1217
+ onAction: onToolbarAction,
1218
+ codeLang,
1219
+ inCodeBlock: activeFormats.has("block:pre"),
1220
+ onCodeLangChange: changeCodeLang
548
1221
  }
549
1222
  ),
550
1223
  /* @__PURE__ */ jsxs("div", { className: "lib-xplat-editor__content", style: { minHeight }, children: [
@@ -555,9 +1228,14 @@ var Editor = (props) => {
555
1228
  ref: editorRef,
556
1229
  contentEditable: !readOnly,
557
1230
  suppressContentEditableWarning: true,
1231
+ role: "textbox",
1232
+ "aria-multiline": "true",
1233
+ "aria-readonly": readOnly || void 0,
1234
+ "aria-label": placeholder,
558
1235
  className: "lib-xplat-editor__editable",
559
1236
  onInput: handleInput,
560
1237
  onKeyDown: handleKeyDown,
1238
+ onClick: handleEditorClick,
561
1239
  onKeyUp: handleSelectionUpdate,
562
1240
  onMouseUp: handleSelectionUpdate,
563
1241
  onPaste: handlePaste,
@@ -599,14 +1277,27 @@ var TOOLBAR_LABEL = {
599
1277
  underline: "U",
600
1278
  strikethrough: "S",
601
1279
  code: "<>",
602
- heading: "H",
1280
+ "heading-1": "H1",
1281
+ "heading-2": "H2",
1282
+ "heading-3": "H3",
603
1283
  list: "\u2022",
604
1284
  "ordered-list": "1.",
605
1285
  blockquote: "\u275D",
606
1286
  "code-block": "[ ]",
607
1287
  link: "\u{1F517}",
608
1288
  image: "\u{1F5BC}",
609
- divider: "\u2014"
1289
+ divider: "\u2014",
1290
+ checklist: "\u2611",
1291
+ "align-left": "\u21E4",
1292
+ "align-center": "\u2194",
1293
+ "align-right": "\u21E5",
1294
+ highlight: "\u{1F58D}",
1295
+ superscript: "x\xB2",
1296
+ subscript: "x\u2082",
1297
+ table: "\u25A6",
1298
+ "clear-format": "\u232B",
1299
+ undo: "\u21B6",
1300
+ redo: "\u21B7"
610
1301
  };
611
1302
  var TOOLBAR_TITLE = {
612
1303
  bold: "\uAD75\uAC8C (\u2318B)",
@@ -614,36 +1305,74 @@ var TOOLBAR_TITLE = {
614
1305
  underline: "\uBC11\uC904 (\u2318U)",
615
1306
  strikethrough: "\uCDE8\uC18C\uC120",
616
1307
  code: "\uC778\uB77C\uC778 \uCF54\uB4DC",
617
- heading: "\uC81C\uBAA9",
1308
+ "heading-1": "\uC81C\uBAA9 1",
1309
+ "heading-2": "\uC81C\uBAA9 2",
1310
+ "heading-3": "\uC81C\uBAA9 3",
618
1311
  list: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D",
619
1312
  "ordered-list": "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D",
620
1313
  blockquote: "\uC778\uC6A9",
621
1314
  "code-block": "\uCF54\uB4DC \uBE14\uB85D",
622
1315
  link: "\uB9C1\uD06C (\u2318K)",
623
1316
  image: "\uC774\uBBF8\uC9C0",
624
- divider: "\uAD6C\uBD84\uC120"
1317
+ divider: "\uAD6C\uBD84\uC120",
1318
+ checklist: "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
1319
+ "align-left": "\uC67C\uCABD \uC815\uB82C",
1320
+ "align-center": "\uAC00\uC6B4\uB370 \uC815\uB82C",
1321
+ "align-right": "\uC624\uB978\uCABD \uC815\uB82C",
1322
+ highlight: "\uD615\uAD11\uD39C",
1323
+ superscript: "\uC704 \uCCA8\uC790",
1324
+ subscript: "\uC544\uB798 \uCCA8\uC790",
1325
+ table: "\uD45C \uC0BD\uC785",
1326
+ "clear-format": "\uC11C\uC2DD \uC9C0\uC6B0\uAE30",
1327
+ undo: "\uB418\uB3CC\uB9AC\uAE30 (\u2318Z)",
1328
+ redo: "\uB2E4\uC2DC\uD558\uAE30 (\u2318\u21E7Z)"
625
1329
  };
626
1330
  var isActive = (item, active) => {
627
- if (item === "heading") return active.has("block:h1") || active.has("block:h2") || active.has("block:h3");
1331
+ if (item === "heading-1") return active.has("block:h1");
1332
+ if (item === "heading-2") return active.has("block:h2");
1333
+ if (item === "heading-3") return active.has("block:h3");
628
1334
  if (item === "blockquote") return active.has("block:blockquote");
629
1335
  if (item === "code-block") return active.has("block:pre");
1336
+ if (item === "align-left") return active.has("align:left");
1337
+ if (item === "align-center") return active.has("align:center");
1338
+ if (item === "align-right") return active.has("align:right");
630
1339
  return active.has(item);
631
1340
  };
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);
1341
+ var EditorToolbar = ({
1342
+ items,
1343
+ active,
1344
+ onAction,
1345
+ codeLang,
1346
+ inCodeBlock,
1347
+ onCodeLangChange
1348
+ }) => {
1349
+ return /* @__PURE__ */ jsxs("div", { className: "lib-xplat-editor__toolbar", children: [
1350
+ items.map((item) => /* @__PURE__ */ jsx(
1351
+ "button",
1352
+ {
1353
+ type: "button",
1354
+ className: clsx_default("lib-xplat-editor__toolbar-btn", isActive(item, active) && "active"),
1355
+ title: TOOLBAR_TITLE[item],
1356
+ onMouseDown: (e) => {
1357
+ e.preventDefault();
1358
+ onAction(item);
1359
+ },
1360
+ children: TOOLBAR_LABEL[item]
642
1361
  },
643
- children: TOOLBAR_LABEL[item]
644
- },
645
- item
646
- )) });
1362
+ item
1363
+ )),
1364
+ inCodeBlock && /* @__PURE__ */ jsx(
1365
+ "select",
1366
+ {
1367
+ className: "lib-xplat-editor__lang",
1368
+ value: codeLang,
1369
+ "aria-label": "\uCF54\uB4DC \uC5B8\uC5B4",
1370
+ onMouseDown: (e) => e.stopPropagation(),
1371
+ onChange: (e) => onCodeLangChange(e.target.value),
1372
+ children: CODE_LANGUAGES.map((l) => /* @__PURE__ */ jsx("option", { value: l.value, children: l.label }, l.value))
1373
+ }
1374
+ )
1375
+ ] });
647
1376
  };
648
1377
  var SlashMenu = ({ position, items, activeIndex, onSelect, onClose }) => {
649
1378
  React.useEffect(() => {