@x-plat/design-system 0.11.0 → 0.13.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 (63) hide show
  1. package/dist/components/AutoResizeTextArea/index.cjs +2 -2
  2. package/dist/components/AutoResizeTextArea/index.js +2 -2
  3. package/dist/components/ChatInput/index.cjs +2 -2
  4. package/dist/components/ChatInput/index.js +2 -2
  5. package/dist/components/CheckBox/index.cjs +2 -2
  6. package/dist/components/CheckBox/index.js +2 -2
  7. package/dist/components/DatePicker/index.cjs +109 -75
  8. package/dist/components/DatePicker/index.d.cts +19 -0
  9. package/dist/components/DatePicker/index.d.ts +19 -0
  10. package/dist/components/DatePicker/index.js +109 -75
  11. package/dist/components/Dropdown/index.cjs +22 -9
  12. package/dist/components/Dropdown/index.css +3 -0
  13. package/dist/components/Dropdown/index.js +22 -9
  14. package/dist/components/Editor/index.cjs +602 -30
  15. package/dist/components/Editor/index.css +103 -0
  16. package/dist/components/Editor/index.d.cts +1 -1
  17. package/dist/components/Editor/index.d.ts +1 -1
  18. package/dist/components/Editor/index.js +602 -30
  19. package/dist/components/FieldMessage/index.cjs +2 -2
  20. package/dist/components/FieldMessage/index.d.cts +8 -0
  21. package/dist/components/FieldMessage/index.d.ts +8 -0
  22. package/dist/components/FieldMessage/index.js +2 -2
  23. package/dist/components/FileUpload/index.cjs +10 -6
  24. package/dist/components/FileUpload/index.d.cts +15 -2
  25. package/dist/components/FileUpload/index.d.ts +15 -2
  26. package/dist/components/FileUpload/index.js +10 -6
  27. package/dist/components/ImageSelector/index.cjs +7 -6
  28. package/dist/components/ImageSelector/index.d.cts +15 -2
  29. package/dist/components/ImageSelector/index.d.ts +15 -2
  30. package/dist/components/ImageSelector/index.js +7 -6
  31. package/dist/components/Input/index.cjs +2 -2
  32. package/dist/components/Input/index.d.cts +4 -0
  33. package/dist/components/Input/index.d.ts +4 -0
  34. package/dist/components/Input/index.js +2 -2
  35. package/dist/components/PopOver/index.cjs +21 -7
  36. package/dist/components/PopOver/index.css +3 -0
  37. package/dist/components/PopOver/index.js +21 -7
  38. package/dist/components/Radio/index.cjs +2 -2
  39. package/dist/components/Radio/index.js +2 -2
  40. package/dist/components/Select/index.cjs +23 -10
  41. package/dist/components/Select/index.css +3 -0
  42. package/dist/components/Select/index.d.cts +1 -2
  43. package/dist/components/Select/index.d.ts +1 -2
  44. package/dist/components/Select/index.js +23 -10
  45. package/dist/components/Switch/index.cjs +2 -2
  46. package/dist/components/Switch/index.js +2 -2
  47. package/dist/components/TextArea/index.cjs +2 -2
  48. package/dist/components/TextArea/index.js +2 -2
  49. package/dist/components/TimePicker/index.cjs +23 -10
  50. package/dist/components/TimePicker/index.css +3 -0
  51. package/dist/components/TimePicker/index.js +23 -10
  52. package/dist/components/index.cjs +751 -124
  53. package/dist/components/index.css +113 -0
  54. package/dist/components/index.js +751 -124
  55. package/dist/index.cjs +751 -124
  56. package/dist/index.css +113 -0
  57. package/dist/index.js +751 -124
  58. package/guidelines/Guidelines.md +12 -0
  59. package/guidelines/MIGRATION.md +270 -0
  60. package/guidelines/components/datepicker.md +26 -0
  61. package/guidelines/components/editor.md +58 -3
  62. package/guidelines/components/file-media.md +21 -4
  63. package/package.json +1 -1
@@ -53,6 +53,109 @@ function clsx() {
53
53
  }
54
54
  var clsx_default = clsx;
55
55
 
56
+ // src/components/Editor/highlight.ts
57
+ var CODE_LANGUAGES = [
58
+ { value: "plain", label: "\uC5C6\uC74C" },
59
+ { value: "javascript", label: "JavaScript" },
60
+ { value: "typescript", label: "TypeScript" },
61
+ { value: "json", label: "JSON" },
62
+ { value: "html", label: "HTML" },
63
+ { value: "css", label: "CSS" },
64
+ { value: "python", label: "Python" },
65
+ { value: "sql", label: "SQL" },
66
+ { value: "bash", label: "Bash" }
67
+ ];
68
+ var KEYWORDS = {
69
+ plain: [],
70
+ 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(" "),
71
+ 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(" "),
72
+ json: ["true", "false", "null"],
73
+ html: [],
74
+ css: [],
75
+ 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(" "),
76
+ 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(" "),
77
+ 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(" ")
78
+ };
79
+ var LINE_COMMENT = {
80
+ javascript: "//",
81
+ typescript: "//",
82
+ css: "",
83
+ python: "#",
84
+ sql: "--",
85
+ bash: "#"
86
+ };
87
+ var escape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
88
+ var wrap = (cls, text) => `<span class="tok-${cls}">${escape(text)}</span>`;
89
+ var highlight = (code, lang) => {
90
+ if (lang === "plain" || !code) return escape(code);
91
+ const keywords = new Set(KEYWORDS[lang]);
92
+ const lineComment = LINE_COMMENT[lang];
93
+ const caseInsensitive = lang === "sql";
94
+ let out = "";
95
+ let i = 0;
96
+ while (i < code.length) {
97
+ const rest = code.slice(i);
98
+ if ((lang === "javascript" || lang === "typescript" || lang === "css") && rest.startsWith("/*")) {
99
+ const end = code.indexOf("*/", i + 2);
100
+ const stop = end === -1 ? code.length : end + 2;
101
+ out += wrap("comment", code.slice(i, stop));
102
+ i = stop;
103
+ continue;
104
+ }
105
+ if (lineComment && rest.startsWith(lineComment)) {
106
+ const nl = code.indexOf("\n", i);
107
+ const stop = nl === -1 ? code.length : nl;
108
+ out += wrap("comment", code.slice(i, stop));
109
+ i = stop;
110
+ continue;
111
+ }
112
+ const q = rest[0];
113
+ if (q === '"' || q === "'" || q === "`") {
114
+ let j = 1;
115
+ while (j < rest.length) {
116
+ if (rest[j] === "\\") {
117
+ j += 2;
118
+ continue;
119
+ }
120
+ if (rest[j] === q) {
121
+ j += 1;
122
+ break;
123
+ }
124
+ if (rest[j] === "\n" && q !== "`") break;
125
+ j += 1;
126
+ }
127
+ out += wrap("string", rest.slice(0, j));
128
+ i += j;
129
+ continue;
130
+ }
131
+ const num = /^\d+(\.\d+)?/.exec(rest);
132
+ if (num && !/[\w$]/.test(code[i - 1] ?? "")) {
133
+ out += wrap("number", num[0]);
134
+ i += num[0].length;
135
+ continue;
136
+ }
137
+ const word = /^[A-Za-z_$][\w$]*/.exec(rest);
138
+ if (word) {
139
+ const w = word[0];
140
+ const hit = caseInsensitive ? keywords.has(w.toUpperCase()) : keywords.has(w);
141
+ out += hit ? wrap("keyword", w) : escape(w);
142
+ i += w.length;
143
+ continue;
144
+ }
145
+ if (lang === "html" && rest.startsWith("<")) {
146
+ const tag = /^<\/?[A-Za-z][\w-]*/.exec(rest);
147
+ if (tag) {
148
+ out += wrap("keyword", tag[0]);
149
+ i += tag[0].length;
150
+ continue;
151
+ }
152
+ }
153
+ out += escape(code[i]);
154
+ i += 1;
155
+ }
156
+ return out;
157
+ };
158
+
56
159
  // src/components/Editor/Editor.tsx
57
160
  var import_jsx_runtime = require("react/jsx-runtime");
58
161
  var DEFAULT_TOOLBAR = [
@@ -61,14 +164,27 @@ var DEFAULT_TOOLBAR = [
61
164
  "underline",
62
165
  "strikethrough",
63
166
  "code",
64
- "heading",
167
+ "heading-1",
168
+ "heading-2",
169
+ "heading-3",
65
170
  "list",
66
171
  "ordered-list",
67
172
  "blockquote",
68
173
  "code-block",
69
174
  "link",
70
175
  "image",
71
- "divider"
176
+ "divider",
177
+ "checklist",
178
+ "table",
179
+ "align-left",
180
+ "align-center",
181
+ "align-right",
182
+ "highlight",
183
+ "superscript",
184
+ "subscript",
185
+ "clear-format",
186
+ "undo",
187
+ "redo"
72
188
  ];
73
189
  var ALLOWED_TAGS = /* @__PURE__ */ new Set([
74
190
  "p",
@@ -97,11 +213,41 @@ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
97
213
  "img",
98
214
  "hr",
99
215
  "span",
100
- "div"
216
+ "div",
217
+ "sub",
218
+ "sup",
219
+ "mark",
220
+ "table",
221
+ "thead",
222
+ "tbody",
223
+ "tr",
224
+ "th",
225
+ "td"
101
226
  ]);
102
227
  var ALLOWED_ATTRS = {
103
228
  a: ["href", "target", "rel"],
104
- img: ["src", "alt"]
229
+ img: ["src", "alt", "width"],
230
+ // 정렬·체크리스트는 클래스로만 표현한다. style 을 열면 붙여넣기로 아무 CSS 나 들어온다
231
+ p: ["class"],
232
+ h1: ["class"],
233
+ h2: ["class"],
234
+ h3: ["class"],
235
+ h4: ["class"],
236
+ h5: ["class"],
237
+ h6: ["class"],
238
+ ul: ["class"],
239
+ ol: ["class"],
240
+ li: ["class", "data-checked"],
241
+ blockquote: ["class"],
242
+ div: ["class"],
243
+ span: ["class"],
244
+ pre: ["class"],
245
+ code: ["class"],
246
+ mark: ["class"],
247
+ table: ["class"],
248
+ td: ["class"],
249
+ th: ["class"],
250
+ tr: ["class"]
105
251
  };
106
252
  var DROP_WITH_CONTENT = /* @__PURE__ */ new Set([
107
253
  "script",
@@ -154,7 +300,9 @@ var escapeHtml = (text) => {
154
300
  };
155
301
  var SLASH_ITEMS = [
156
302
  { key: "paragraph", label: "\uB2E8\uB77D", hint: "\uAE30\uBCF8 \uD14D\uC2A4\uD2B8" },
157
- { key: "heading", label: "\uC81C\uBAA9 1", hint: "\uD070 \uC81C\uBAA9" },
303
+ { key: "heading-1", label: "\uC81C\uBAA9 1", hint: "\uAC00\uC7A5 \uD070 \uC81C\uBAA9" },
304
+ { key: "heading-2", label: "\uC81C\uBAA9 2", hint: "" },
305
+ { key: "heading-3", label: "\uC81C\uBAA9 3", hint: "" },
158
306
  { key: "list", label: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D", hint: "\u2022 \uD56D\uBAA9" },
159
307
  { key: "ordered-list", label: "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D", hint: "1. \uD56D\uBAA9" },
160
308
  { key: "blockquote", label: "\uC778\uC6A9", hint: "" },
@@ -197,6 +345,8 @@ var Editor = (props) => {
197
345
  el.innerHTML = sanitizeHtml(value || "");
198
346
  if (!el.firstChild) el.innerHTML = "<p><br></p>";
199
347
  lastEmittedRef.current = null;
348
+ historyRef.current = [{ html: el.innerHTML, caret: 0 }];
349
+ historyIdxRef.current = 0;
200
350
  updateEmpty();
201
351
  }, [value]);
202
352
  const updateEmpty = () => {
@@ -241,6 +391,17 @@ var Editor = (props) => {
241
391
  if (document.queryCommandState("insertOrderedList")) next.add("ordered-list");
242
392
  const block = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
243
393
  if (block) next.add(`block:${block}`);
394
+ if (document.queryCommandState("superscript")) next.add("superscript");
395
+ if (document.queryCommandState("subscript")) next.add("subscript");
396
+ } catch {
397
+ }
398
+ try {
399
+ const blockEl = currentBlock();
400
+ if (blockEl?.classList.contains("align-center")) next.add("align:center");
401
+ else if (blockEl?.classList.contains("align-right")) next.add("align:right");
402
+ else if (blockEl) next.add("align:left");
403
+ if (blockEl?.closest("mark")) next.add("highlight");
404
+ if (blockEl?.closest("ul.checklist")) next.add("checklist");
244
405
  } catch {
245
406
  }
246
407
  setActiveFormats(next);
@@ -254,6 +415,122 @@ var Editor = (props) => {
254
415
  const setBlock = (tag) => {
255
416
  exec("formatBlock", tag);
256
417
  };
418
+ const toggleBlock = (tag) => {
419
+ const current = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
420
+ setBlock(current === tag ? "p" : tag);
421
+ };
422
+ const historyRef = import_react.default.useRef([]);
423
+ const historyIdxRef = import_react.default.useRef(-1);
424
+ const lastPushAtRef = import_react.default.useRef(0);
425
+ const isRestoringRef = import_react.default.useRef(false);
426
+ const caretOffset = (rootEl) => {
427
+ const el = rootEl ?? editorRef.current;
428
+ const sel = window.getSelection();
429
+ if (!el || !sel || sel.rangeCount === 0 || !el.contains(sel.anchorNode)) return 0;
430
+ const range = sel.getRangeAt(0).cloneRange();
431
+ range.selectNodeContents(el);
432
+ range.setEnd(sel.getRangeAt(0).endContainer, sel.getRangeAt(0).endOffset);
433
+ return range.toString().length;
434
+ };
435
+ const setCaretOffset = (offset, rootEl) => {
436
+ const el = rootEl ?? editorRef.current;
437
+ if (!el) return;
438
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
439
+ let remaining = offset;
440
+ let node = null;
441
+ while (node = walker.nextNode()) {
442
+ const len = node.textContent?.length ?? 0;
443
+ if (remaining <= len) {
444
+ const range = document.createRange();
445
+ range.setStart(node, remaining);
446
+ range.collapse(true);
447
+ const sel = window.getSelection();
448
+ sel?.removeAllRanges();
449
+ sel?.addRange(range);
450
+ return;
451
+ }
452
+ remaining -= len;
453
+ }
454
+ placeCaretAtEnd(el);
455
+ };
456
+ const pushHistory = (force = false) => {
457
+ const el = editorRef.current;
458
+ if (!el || isRestoringRef.current) return;
459
+ const html = el.innerHTML;
460
+ const hist = historyRef.current;
461
+ const cur = hist[historyIdxRef.current];
462
+ if (cur && cur.html === html) return;
463
+ const now = Date.now();
464
+ const coalesce = !force && cur && now - lastPushAtRef.current < 500;
465
+ lastPushAtRef.current = now;
466
+ if (coalesce) {
467
+ hist[historyIdxRef.current] = { html, caret: caretOffset() };
468
+ return;
469
+ }
470
+ hist.length = historyIdxRef.current + 1;
471
+ hist.push({ html, caret: caretOffset() });
472
+ if (hist.length > 200) hist.shift();
473
+ historyIdxRef.current = hist.length - 1;
474
+ };
475
+ const applySnapshot = (snap) => {
476
+ const el = editorRef.current;
477
+ if (!el) return;
478
+ isRestoringRef.current = true;
479
+ el.innerHTML = snap.html;
480
+ setCaretOffset(snap.caret);
481
+ isRestoringRef.current = false;
482
+ lastEmittedRef.current = el.innerHTML;
483
+ onChange?.(el.innerHTML);
484
+ updateEmpty();
485
+ updateActiveFormats();
486
+ };
487
+ const undo = () => {
488
+ pushHistory(true);
489
+ if (historyIdxRef.current <= 0) return;
490
+ historyIdxRef.current -= 1;
491
+ applySnapshot(historyRef.current[historyIdxRef.current]);
492
+ };
493
+ const redo = () => {
494
+ if (historyIdxRef.current >= historyRef.current.length - 1) return;
495
+ historyIdxRef.current += 1;
496
+ applySnapshot(historyRef.current[historyIdxRef.current]);
497
+ };
498
+ const langOf = (pre) => {
499
+ const cls = Array.from(pre.classList).find((c) => c.startsWith("lang-"));
500
+ return cls?.slice(5) ?? "plain";
501
+ };
502
+ const [codeLang, setCodeLang] = import_react.default.useState("plain");
503
+ const highlightRafRef = import_react.default.useRef(0);
504
+ const rehighlight = (pre) => {
505
+ if (composingRef.current) return;
506
+ const code = pre.querySelector("code");
507
+ if (!code) return;
508
+ const lang = langOf(pre);
509
+ const text = code.textContent || "";
510
+ const next = highlight(text, lang);
511
+ if (code.innerHTML === next) return;
512
+ const caret = caretOffset(code);
513
+ isRestoringRef.current = true;
514
+ code.innerHTML = next;
515
+ setCaretOffset(caret, code);
516
+ isRestoringRef.current = false;
517
+ };
518
+ const scheduleHighlight = () => {
519
+ const pre = closestPre();
520
+ if (!pre) return;
521
+ cancelAnimationFrame(highlightRafRef.current);
522
+ highlightRafRef.current = requestAnimationFrame(() => rehighlight(pre));
523
+ };
524
+ const changeCodeLang = (lang) => {
525
+ const pre = closestPre();
526
+ if (!pre) return;
527
+ pushHistory(true);
528
+ Array.from(pre.classList).filter((c) => c.startsWith("lang-")).forEach((c) => pre.classList.remove(c));
529
+ if (lang !== "plain") pre.classList.add(`lang-${lang}`);
530
+ setCodeLang(lang);
531
+ rehighlight(pre);
532
+ handleInput();
533
+ };
257
534
  const closestPre = () => {
258
535
  const sel = window.getSelection();
259
536
  const node = sel?.anchorNode;
@@ -262,6 +539,7 @@ var Editor = (props) => {
262
539
  return el?.closest("pre") ?? null;
263
540
  };
264
541
  const insertCodeBlock = () => {
542
+ pushHistory(true);
265
543
  const el = editorRef.current;
266
544
  const sel = window.getSelection();
267
545
  if (!el || !sel || sel.rangeCount === 0) return;
@@ -344,7 +622,167 @@ var Editor = (props) => {
344
622
  sel.addRange(r2);
345
623
  editorRef.current?.focus();
346
624
  };
625
+ const clearFormat = () => {
626
+ exec("removeFormat");
627
+ exec("unlink");
628
+ setBlock("p");
629
+ };
630
+ const indentList = (outdent) => {
631
+ exec(outdent ? "outdent" : "indent");
632
+ };
633
+ const inListItem = () => {
634
+ const sel = window.getSelection();
635
+ const node = sel?.anchorNode;
636
+ if (!node || !editorRef.current?.contains(node)) return false;
637
+ const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
638
+ return !!el?.closest("li");
639
+ };
640
+ const currentBlock = () => {
641
+ const sel = window.getSelection();
642
+ const node = sel?.anchorNode;
643
+ const root = editorRef.current;
644
+ if (!node || !root?.contains(node)) return null;
645
+ let el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
646
+ while (el && el !== root && !/^(P|H[1-6]|LI|BLOCKQUOTE|PRE|DIV|TD|TH)$/.test(el.tagName)) {
647
+ el = el.parentElement;
648
+ }
649
+ return el && el !== root ? el : null;
650
+ };
651
+ const setAlign = (align) => {
652
+ const block = currentBlock();
653
+ if (!block) return;
654
+ pushHistory(true);
655
+ block.classList.remove("align-left", "align-center", "align-right");
656
+ if (align !== "left") block.classList.add(`align-${align}`);
657
+ handleInput();
658
+ };
659
+ const toggleChecklist = () => {
660
+ pushHistory(true);
661
+ const block = currentBlock();
662
+ const list = block?.closest("ul");
663
+ if (list?.classList.contains("checklist")) {
664
+ list.classList.remove("checklist");
665
+ list.querySelectorAll("li").forEach((li) => li.removeAttribute("data-checked"));
666
+ handleInput();
667
+ return;
668
+ }
669
+ if (list) {
670
+ list.classList.add("checklist");
671
+ list.querySelectorAll("li").forEach((li) => li.setAttribute("data-checked", "false"));
672
+ handleInput();
673
+ return;
674
+ }
675
+ exec("insertUnorderedList");
676
+ const made = currentBlock()?.closest("ul");
677
+ if (made) {
678
+ made.classList.add("checklist");
679
+ made.querySelectorAll("li").forEach((li) => li.setAttribute("data-checked", "false"));
680
+ }
681
+ handleInput();
682
+ };
683
+ const handleEditorClick = (e) => {
684
+ if (readOnly) return;
685
+ const target = e.target;
686
+ const li = target.closest?.("li");
687
+ if (!li || !li.closest("ul.checklist")) return;
688
+ const rect = li.getBoundingClientRect();
689
+ if (e.clientX - rect.left > 20) return;
690
+ e.preventDefault();
691
+ pushHistory(true);
692
+ li.setAttribute("data-checked", li.getAttribute("data-checked") === "true" ? "false" : "true");
693
+ handleInput();
694
+ };
695
+ const toggleHighlight = () => {
696
+ const sel = window.getSelection();
697
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
698
+ pushHistory(true);
699
+ const range = sel.getRangeAt(0);
700
+ const node = sel.anchorNode;
701
+ const el = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement;
702
+ const existing = el?.closest("mark");
703
+ if (existing) {
704
+ const parent = existing.parentNode;
705
+ while (existing.firstChild) parent?.insertBefore(existing.firstChild, existing);
706
+ parent?.removeChild(existing);
707
+ handleInput();
708
+ return;
709
+ }
710
+ const mark = document.createElement("mark");
711
+ try {
712
+ range.surroundContents(mark);
713
+ } catch {
714
+ const frag = range.extractContents();
715
+ mark.appendChild(frag);
716
+ range.insertNode(mark);
717
+ }
718
+ handleInput();
719
+ };
720
+ const insertTable = (cols = 3, rows = 2) => {
721
+ const sel = window.getSelection();
722
+ if (!sel || sel.rangeCount === 0) return;
723
+ pushHistory(true);
724
+ const table = document.createElement("table");
725
+ const thead = document.createElement("thead");
726
+ const headRow = document.createElement("tr");
727
+ for (let c = 0; c < cols; c++) {
728
+ const th = document.createElement("th");
729
+ th.innerHTML = "<br>";
730
+ headRow.appendChild(th);
731
+ }
732
+ thead.appendChild(headRow);
733
+ table.appendChild(thead);
734
+ const tbody = document.createElement("tbody");
735
+ for (let r2 = 0; r2 < rows; r2++) {
736
+ const tr = document.createElement("tr");
737
+ for (let c = 0; c < cols; c++) {
738
+ const td = document.createElement("td");
739
+ td.innerHTML = "<br>";
740
+ tr.appendChild(td);
741
+ }
742
+ tbody.appendChild(tr);
743
+ }
744
+ table.appendChild(tbody);
745
+ const range = sel.getRangeAt(0);
746
+ range.deleteContents();
747
+ range.insertNode(table);
748
+ ensureParagraphAfter(table);
749
+ const first = table.querySelector("th");
750
+ if (first) placeCaretAtEnd(first);
751
+ handleInput();
752
+ };
753
+ const moveTableCell = (backwards) => {
754
+ const block = currentBlock();
755
+ const cell = block?.closest("td, th");
756
+ if (!cell) return false;
757
+ const table = cell.closest("table");
758
+ if (!table) return false;
759
+ const cells = Array.from(table.querySelectorAll("td, th"));
760
+ const i = cells.indexOf(cell);
761
+ const next = cells[i + (backwards ? -1 : 1)];
762
+ if (next) {
763
+ placeCaretAtEnd(next);
764
+ return true;
765
+ }
766
+ if (!backwards) {
767
+ pushHistory(true);
768
+ const lastRow = table.querySelector("tbody")?.lastElementChild;
769
+ if (lastRow) {
770
+ const tr = document.createElement("tr");
771
+ for (let c = 0; c < lastRow.children.length; c++) {
772
+ const td = document.createElement("td");
773
+ td.innerHTML = "<br>";
774
+ tr.appendChild(td);
775
+ }
776
+ lastRow.after(tr);
777
+ placeCaretAtEnd(tr.firstElementChild);
778
+ handleInput();
779
+ return true;
780
+ }
781
+ }
782
+ return false;
783
+ };
347
784
  const insertDivider = () => {
785
+ pushHistory(true);
348
786
  exec("insertHorizontalRule");
349
787
  const el = editorRef.current;
350
788
  if (el && el.lastElementChild?.tagName === "HR") {
@@ -437,8 +875,14 @@ var Editor = (props) => {
437
875
  case "paragraph":
438
876
  setBlock("p");
439
877
  break;
440
- case "heading":
441
- setBlock("h2");
878
+ case "heading-1":
879
+ toggleBlock("h1");
880
+ break;
881
+ case "heading-2":
882
+ toggleBlock("h2");
883
+ break;
884
+ case "heading-3":
885
+ toggleBlock("h3");
442
886
  break;
443
887
  case "list":
444
888
  exec("insertUnorderedList");
@@ -531,6 +975,8 @@ var Editor = (props) => {
531
975
  if (!el) return;
532
976
  const html = el.innerHTML;
533
977
  lastEmittedRef.current = html;
978
+ pushHistory();
979
+ scheduleHighlight();
534
980
  onChange?.(html);
535
981
  updateEmpty();
536
982
  updateActiveFormats();
@@ -584,6 +1030,15 @@ var Editor = (props) => {
584
1030
  return;
585
1031
  }
586
1032
  }
1033
+ if (e.key === "Tab" && moveTableCell(e.shiftKey)) {
1034
+ e.preventDefault();
1035
+ return;
1036
+ }
1037
+ if (e.key === "Tab" && inListItem()) {
1038
+ e.preventDefault();
1039
+ indentList(e.shiftKey);
1040
+ return;
1041
+ }
587
1042
  if (slashOpen) {
588
1043
  if (e.key === "ArrowDown") {
589
1044
  e.preventDefault();
@@ -607,6 +1062,21 @@ var Editor = (props) => {
607
1062
  return;
608
1063
  }
609
1064
  }
1065
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "z") {
1066
+ e.preventDefault();
1067
+ if (e.shiftKey) redo();
1068
+ else undo();
1069
+ return;
1070
+ }
1071
+ if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "v") {
1072
+ plainPasteRef.current = true;
1073
+ return;
1074
+ }
1075
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "y") {
1076
+ e.preventDefault();
1077
+ redo();
1078
+ return;
1079
+ }
610
1080
  if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
611
1081
  const k = e.key.toLowerCase();
612
1082
  if (k === "b") {
@@ -640,10 +1110,16 @@ var Editor = (props) => {
640
1110
  }
641
1111
  }
642
1112
  };
1113
+ const plainPasteRef = import_react.default.useRef(false);
643
1114
  const handlePaste = (e) => {
644
1115
  if (readOnly) return;
645
1116
  e.preventDefault();
646
1117
  const text = e.clipboardData.getData("text/plain");
1118
+ if (plainPasteRef.current) {
1119
+ plainPasteRef.current = false;
1120
+ insertTextAtCaret(text);
1121
+ return;
1122
+ }
647
1123
  if (closestPre()) {
648
1124
  insertTextAtCaret(text);
649
1125
  return;
@@ -670,9 +1146,11 @@ var Editor = (props) => {
670
1146
  };
671
1147
  const handleSelectionUpdate = () => {
672
1148
  updateActiveFormats();
1149
+ const pre = closestPre();
1150
+ setCodeLang(pre ? langOf(pre) : "plain");
673
1151
  };
674
1152
  const onToolbarAction = (item) => {
675
- if (closestPre() && item !== "code-block") return;
1153
+ if (closestPre() && !["code-block", "undo", "redo"].includes(item)) return;
676
1154
  switch (item) {
677
1155
  case "bold":
678
1156
  exec("bold");
@@ -701,8 +1179,14 @@ var Editor = (props) => {
701
1179
  handleInput();
702
1180
  break;
703
1181
  }
704
- case "heading":
705
- setBlock("h2");
1182
+ case "heading-1":
1183
+ toggleBlock("h1");
1184
+ break;
1185
+ case "heading-2":
1186
+ toggleBlock("h2");
1187
+ break;
1188
+ case "heading-3":
1189
+ toggleBlock("h3");
706
1190
  break;
707
1191
  case "list":
708
1192
  exec("insertUnorderedList");
@@ -725,6 +1209,39 @@ var Editor = (props) => {
725
1209
  case "divider":
726
1210
  insertDivider();
727
1211
  break;
1212
+ case "checklist":
1213
+ toggleChecklist();
1214
+ break;
1215
+ case "align-left":
1216
+ setAlign("left");
1217
+ break;
1218
+ case "align-center":
1219
+ setAlign("center");
1220
+ break;
1221
+ case "align-right":
1222
+ setAlign("right");
1223
+ break;
1224
+ case "highlight":
1225
+ toggleHighlight();
1226
+ break;
1227
+ case "superscript":
1228
+ exec("superscript");
1229
+ break;
1230
+ case "subscript":
1231
+ exec("subscript");
1232
+ break;
1233
+ case "table":
1234
+ insertTable();
1235
+ break;
1236
+ case "clear-format":
1237
+ clearFormat();
1238
+ break;
1239
+ case "undo":
1240
+ undo();
1241
+ break;
1242
+ case "redo":
1243
+ redo();
1244
+ break;
728
1245
  }
729
1246
  };
730
1247
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: clsx_default("lib-xplat-editor", isFocused && "focused", readOnly && "readonly"), children: [
@@ -733,7 +1250,10 @@ var Editor = (props) => {
733
1250
  {
734
1251
  items: toolbar,
735
1252
  active: activeFormats,
736
- onAction: onToolbarAction
1253
+ onAction: onToolbarAction,
1254
+ codeLang,
1255
+ inCodeBlock: activeFormats.has("block:pre"),
1256
+ onCodeLangChange: changeCodeLang
737
1257
  }
738
1258
  ),
739
1259
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lib-xplat-editor__content", style: { minHeight }, children: [
@@ -751,6 +1271,7 @@ var Editor = (props) => {
751
1271
  className: "lib-xplat-editor__editable",
752
1272
  onInput: handleInput,
753
1273
  onKeyDown: handleKeyDown,
1274
+ onClick: handleEditorClick,
754
1275
  onKeyUp: handleSelectionUpdate,
755
1276
  onMouseUp: handleSelectionUpdate,
756
1277
  onPaste: handlePaste,
@@ -792,14 +1313,27 @@ var TOOLBAR_LABEL = {
792
1313
  underline: "U",
793
1314
  strikethrough: "S",
794
1315
  code: "<>",
795
- heading: "H",
1316
+ "heading-1": "H1",
1317
+ "heading-2": "H2",
1318
+ "heading-3": "H3",
796
1319
  list: "\u2022",
797
1320
  "ordered-list": "1.",
798
1321
  blockquote: "\u275D",
799
1322
  "code-block": "[ ]",
800
1323
  link: "\u{1F517}",
801
1324
  image: "\u{1F5BC}",
802
- divider: "\u2014"
1325
+ divider: "\u2014",
1326
+ checklist: "\u2611",
1327
+ "align-left": "\u21E4",
1328
+ "align-center": "\u2194",
1329
+ "align-right": "\u21E5",
1330
+ highlight: "\u{1F58D}",
1331
+ superscript: "x\xB2",
1332
+ subscript: "x\u2082",
1333
+ table: "\u25A6",
1334
+ "clear-format": "\u232B",
1335
+ undo: "\u21B6",
1336
+ redo: "\u21B7"
803
1337
  };
804
1338
  var TOOLBAR_TITLE = {
805
1339
  bold: "\uAD75\uAC8C (\u2318B)",
@@ -807,36 +1341,74 @@ var TOOLBAR_TITLE = {
807
1341
  underline: "\uBC11\uC904 (\u2318U)",
808
1342
  strikethrough: "\uCDE8\uC18C\uC120",
809
1343
  code: "\uC778\uB77C\uC778 \uCF54\uB4DC",
810
- heading: "\uC81C\uBAA9",
1344
+ "heading-1": "\uC81C\uBAA9 1",
1345
+ "heading-2": "\uC81C\uBAA9 2",
1346
+ "heading-3": "\uC81C\uBAA9 3",
811
1347
  list: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D",
812
1348
  "ordered-list": "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D",
813
1349
  blockquote: "\uC778\uC6A9",
814
1350
  "code-block": "\uCF54\uB4DC \uBE14\uB85D",
815
1351
  link: "\uB9C1\uD06C (\u2318K)",
816
1352
  image: "\uC774\uBBF8\uC9C0",
817
- divider: "\uAD6C\uBD84\uC120"
1353
+ divider: "\uAD6C\uBD84\uC120",
1354
+ checklist: "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
1355
+ "align-left": "\uC67C\uCABD \uC815\uB82C",
1356
+ "align-center": "\uAC00\uC6B4\uB370 \uC815\uB82C",
1357
+ "align-right": "\uC624\uB978\uCABD \uC815\uB82C",
1358
+ highlight: "\uD615\uAD11\uD39C",
1359
+ superscript: "\uC704 \uCCA8\uC790",
1360
+ subscript: "\uC544\uB798 \uCCA8\uC790",
1361
+ table: "\uD45C \uC0BD\uC785",
1362
+ "clear-format": "\uC11C\uC2DD \uC9C0\uC6B0\uAE30",
1363
+ undo: "\uB418\uB3CC\uB9AC\uAE30 (\u2318Z)",
1364
+ redo: "\uB2E4\uC2DC\uD558\uAE30 (\u2318\u21E7Z)"
818
1365
  };
819
1366
  var isActive = (item, active) => {
820
- if (item === "heading") return active.has("block:h1") || active.has("block:h2") || active.has("block:h3");
1367
+ if (item === "heading-1") return active.has("block:h1");
1368
+ if (item === "heading-2") return active.has("block:h2");
1369
+ if (item === "heading-3") return active.has("block:h3");
821
1370
  if (item === "blockquote") return active.has("block:blockquote");
822
1371
  if (item === "code-block") return active.has("block:pre");
1372
+ if (item === "align-left") return active.has("align:left");
1373
+ if (item === "align-center") return active.has("align:center");
1374
+ if (item === "align-right") return active.has("align:right");
823
1375
  return active.has(item);
824
1376
  };
825
- var EditorToolbar = ({ items, active, onAction }) => {
826
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lib-xplat-editor__toolbar", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
827
- "button",
828
- {
829
- type: "button",
830
- className: clsx_default("lib-xplat-editor__toolbar-btn", isActive(item, active) && "active"),
831
- title: TOOLBAR_TITLE[item],
832
- onMouseDown: (e) => {
833
- e.preventDefault();
834
- onAction(item);
1377
+ var EditorToolbar = ({
1378
+ items,
1379
+ active,
1380
+ onAction,
1381
+ codeLang,
1382
+ inCodeBlock,
1383
+ onCodeLangChange
1384
+ }) => {
1385
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lib-xplat-editor__toolbar", children: [
1386
+ items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1387
+ "button",
1388
+ {
1389
+ type: "button",
1390
+ className: clsx_default("lib-xplat-editor__toolbar-btn", isActive(item, active) && "active"),
1391
+ title: TOOLBAR_TITLE[item],
1392
+ onMouseDown: (e) => {
1393
+ e.preventDefault();
1394
+ onAction(item);
1395
+ },
1396
+ children: TOOLBAR_LABEL[item]
835
1397
  },
836
- children: TOOLBAR_LABEL[item]
837
- },
838
- item
839
- )) });
1398
+ item
1399
+ )),
1400
+ inCodeBlock && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1401
+ "select",
1402
+ {
1403
+ className: "lib-xplat-editor__lang",
1404
+ value: codeLang,
1405
+ "aria-label": "\uCF54\uB4DC \uC5B8\uC5B4",
1406
+ onMouseDown: (e) => e.stopPropagation(),
1407
+ onChange: (e) => onCodeLangChange(e.target.value),
1408
+ children: CODE_LANGUAGES.map((l) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: l.value, children: l.label }, l.value))
1409
+ }
1410
+ )
1411
+ ] });
840
1412
  };
841
1413
  var SlashMenu = ({ position, items, activeIndex, onSelect, onClose }) => {
842
1414
  import_react.default.useEffect(() => {