@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
package/dist/index.js CHANGED
@@ -5622,11 +5622,11 @@ var FieldMessage = (props) => {
5622
5622
  FieldMessage.displayName = "FieldMessage";
5623
5623
  var FieldMessage_default = FieldMessage;
5624
5624
  var Field = (props) => {
5625
- const { label, required = false, htmlFor, validations, children } = props;
5625
+ const { label, required = false, htmlFor, labelId, validations, children } = props;
5626
5626
  const hasMessages = !!validations && validations.length > 0;
5627
5627
  if (!label && !hasMessages) return /* @__PURE__ */ jsx297(Fragment, { children });
5628
5628
  return /* @__PURE__ */ jsxs191("div", { className: "lib-xplat-field", children: [
5629
- label && /* @__PURE__ */ jsxs191("label", { className: "lib-xplat-field-label", htmlFor, children: [
5629
+ label && /* @__PURE__ */ jsxs191("label", { className: "lib-xplat-field-label", id: labelId, htmlFor, children: [
5630
5630
  label,
5631
5631
  required && /* @__PURE__ */ jsx297("span", { className: "required", "aria-hidden": "true", children: "*" })
5632
5632
  ] }),
@@ -7406,8 +7406,12 @@ var SingleDatePicker = (props) => {
7406
7406
  highlightDates = [],
7407
7407
  locale = "ko",
7408
7408
  rangeStart,
7409
- rangeEnd
7409
+ rangeEnd,
7410
+ label,
7411
+ required,
7412
+ validations
7410
7413
  } = props;
7414
+ const labelId = React12.useId();
7411
7415
  const initialYear = value?.getFullYear();
7412
7416
  const initialMonth = value?.getMonth();
7413
7417
  const { year, month, days, goToPrevMonth, goToNextMonth, setYear, setMonth } = useCalendar(
@@ -7470,10 +7474,12 @@ var SingleDatePicker = (props) => {
7470
7474
  const monthLabels = MONTH_LABELS[locale];
7471
7475
  const titleText = pickerMode === "days" ? locale === "ko" ? `${year}\uB144 ${monthLabels[month]}` : `${monthLabels[month]} ${year}` : pickerMode === "months" ? `${year}` : `${yearRangeStart} - ${yearRangeStart + 11}`;
7472
7476
  const hasRange = rangeStart != null && rangeEnd != null;
7473
- return /* @__PURE__ */ jsxs200(
7477
+ const calendar = /* @__PURE__ */ jsxs200(
7474
7478
  "div",
7475
7479
  {
7476
7480
  className: clsx_default("lib-xplat-datepicker", "single"),
7481
+ role: "group",
7482
+ "aria-labelledby": label ? labelId : void 0,
7477
7483
  children: [
7478
7484
  /* @__PURE__ */ jsxs200("div", { className: "datepicker-header", children: [
7479
7485
  /* @__PURE__ */ jsx314("button", { className: "datepicker-nav", onClick: handlePrev, type: "button", children: /* @__PURE__ */ jsx314(ChevronLeftIcon_default, {}) }),
@@ -7494,18 +7500,18 @@ var SingleDatePicker = (props) => {
7494
7500
  y
7495
7501
  );
7496
7502
  }) }),
7497
- pickerMode === "months" && /* @__PURE__ */ jsx314("div", { className: "datepicker-picker-grid", children: monthLabels.map((label, i) => /* @__PURE__ */ jsx314(
7503
+ pickerMode === "months" && /* @__PURE__ */ jsx314("div", { className: "datepicker-picker-grid", children: monthLabels.map((label2, i) => /* @__PURE__ */ jsx314(
7498
7504
  "button",
7499
7505
  {
7500
7506
  type: "button",
7501
7507
  className: clsx_default("datepicker-picker-cell", i === month && "active"),
7502
7508
  onClick: () => handleMonthSelect(i),
7503
- children: label
7509
+ children: label2
7504
7510
  },
7505
7511
  i
7506
7512
  )) }),
7507
7513
  pickerMode === "days" && /* @__PURE__ */ jsxs200(Fragment4, { children: [
7508
- /* @__PURE__ */ jsx314("div", { className: "datepicker-weekdays", children: weekdays.map((label, i) => /* @__PURE__ */ jsx314(
7514
+ /* @__PURE__ */ jsx314("div", { className: "datepicker-weekdays", children: weekdays.map((label2, i) => /* @__PURE__ */ jsx314(
7509
7515
  "div",
7510
7516
  {
7511
7517
  className: clsx_default(
@@ -7513,9 +7519,9 @@ var SingleDatePicker = (props) => {
7513
7519
  i === 0 && "sunday",
7514
7520
  i === 6 && "saturday"
7515
7521
  ),
7516
- children: label
7522
+ children: label2
7517
7523
  },
7518
- label
7524
+ label2
7519
7525
  )) }),
7520
7526
  /* @__PURE__ */ jsx314("div", { className: "datepicker-grid", children: days.map((day, idx) => {
7521
7527
  const t = day.date.getTime();
@@ -7547,6 +7553,7 @@ var SingleDatePicker = (props) => {
7547
7553
  ]
7548
7554
  }
7549
7555
  );
7556
+ return /* @__PURE__ */ jsx314(Field, { label, required, labelId, validations, children: calendar });
7550
7557
  };
7551
7558
  SingleDatePicker.displayName = "SingleDatePicker";
7552
7559
  var SingleDatePicker_default = SingleDatePicker;
@@ -7561,7 +7568,18 @@ var formatDate = (date) => {
7561
7568
  return `${y}/${m}/${d}`;
7562
7569
  };
7563
7570
  var InputDatePicker = (props) => {
7564
- const { value, onChange, minDate, maxDate, disabled, locale = "ko", placeholder } = props;
7571
+ const {
7572
+ value,
7573
+ onChange,
7574
+ minDate,
7575
+ maxDate,
7576
+ disabled,
7577
+ locale = "ko",
7578
+ placeholder,
7579
+ label,
7580
+ required,
7581
+ validations
7582
+ } = props;
7565
7583
  const [isOpen, setIsOpen] = React13.useState(false);
7566
7584
  const [tempDate, setTempDate] = React13.useState(value ?? /* @__PURE__ */ new Date());
7567
7585
  const handleOpen = () => {
@@ -7587,6 +7605,9 @@ var InputDatePicker = (props) => {
7587
7605
  placeholder,
7588
7606
  suffix: /* @__PURE__ */ jsx315(CalenderIcon_default, {}),
7589
7607
  disabled,
7608
+ label,
7609
+ required,
7610
+ validations,
7590
7611
  readOnly: true
7591
7612
  }
7592
7613
  ) }),
@@ -7718,8 +7739,12 @@ var RangePicker = (props) => {
7718
7739
  onChange,
7719
7740
  minDate,
7720
7741
  maxDate,
7721
- locale = "ko"
7742
+ locale = "ko",
7743
+ label,
7744
+ required,
7745
+ validations
7722
7746
  } = props;
7747
+ const labelId = React16.useId();
7723
7748
  const [activeTab, setActiveTab] = React16.useState("start");
7724
7749
  const handleStartChange = (date) => {
7725
7750
  if (!date) return;
@@ -7733,70 +7758,79 @@ var RangePicker = (props) => {
7733
7758
  };
7734
7759
  const startMaxDate = maxDate && endDate < maxDate ? endDate : endDate;
7735
7760
  const endMinDate = minDate && startDate > minDate ? startDate : startDate;
7736
- return /* @__PURE__ */ jsxs203("div", { className: clsx_default("lib-xplat-datepicker", "range"), children: [
7737
- /* @__PURE__ */ jsx318("div", { className: "datepicker-range-tabs", children: /* @__PURE__ */ jsx318(
7738
- Tab_default,
7739
- {
7740
- activeIndex: activeTab === "start" ? 0 : 1,
7741
- tabs: [
7742
- { value: "start", title: locale === "ko" ? "\uC2DC\uC791\uC77C" : "Start" },
7743
- { value: "end", title: locale === "ko" ? "\uC885\uB8CC\uC77C" : "End" }
7744
- ],
7745
- onChange: (tab) => setActiveTab(tab.value),
7746
- type: "toggle",
7747
- size: "sm"
7748
- }
7749
- ) }),
7750
- /* @__PURE__ */ jsxs203("div", { className: "datepicker-range-panels", children: [
7751
- /* @__PURE__ */ jsx318(
7752
- SingleDatePicker_default,
7753
- {
7754
- value: startDate,
7755
- onChange: handleStartChange,
7756
- minDate,
7757
- maxDate: startMaxDate,
7758
- rangeStart: startDate,
7759
- rangeEnd: endDate,
7760
- locale
7761
- }
7762
- ),
7763
- /* @__PURE__ */ jsx318(
7764
- SingleDatePicker_default,
7765
- {
7766
- value: endDate,
7767
- onChange: handleEndChange,
7768
- minDate: endMinDate,
7769
- maxDate,
7770
- rangeStart: startDate,
7771
- rangeEnd: endDate,
7772
- locale
7773
- }
7774
- )
7775
- ] }),
7776
- /* @__PURE__ */ jsx318("div", { className: "datepicker-range-mobile", children: activeTab === "start" ? /* @__PURE__ */ jsx318(
7777
- SingleDatePicker_default,
7778
- {
7779
- value: startDate,
7780
- onChange: handleStartChange,
7781
- minDate,
7782
- maxDate: startMaxDate,
7783
- rangeStart: startDate,
7784
- rangeEnd: endDate,
7785
- locale
7786
- }
7787
- ) : /* @__PURE__ */ jsx318(
7788
- SingleDatePicker_default,
7789
- {
7790
- value: endDate,
7791
- onChange: handleEndChange,
7792
- minDate: endMinDate,
7793
- maxDate,
7794
- rangeStart: startDate,
7795
- rangeEnd: endDate,
7796
- locale
7797
- }
7798
- ) })
7799
- ] });
7761
+ const calendar = /* @__PURE__ */ jsxs203(
7762
+ "div",
7763
+ {
7764
+ className: clsx_default("lib-xplat-datepicker", "range"),
7765
+ role: "group",
7766
+ "aria-labelledby": label ? labelId : void 0,
7767
+ children: [
7768
+ /* @__PURE__ */ jsx318("div", { className: "datepicker-range-tabs", children: /* @__PURE__ */ jsx318(
7769
+ Tab_default,
7770
+ {
7771
+ activeIndex: activeTab === "start" ? 0 : 1,
7772
+ tabs: [
7773
+ { value: "start", title: locale === "ko" ? "\uC2DC\uC791\uC77C" : "Start" },
7774
+ { value: "end", title: locale === "ko" ? "\uC885\uB8CC\uC77C" : "End" }
7775
+ ],
7776
+ onChange: (tab) => setActiveTab(tab.value),
7777
+ type: "toggle",
7778
+ size: "sm"
7779
+ }
7780
+ ) }),
7781
+ /* @__PURE__ */ jsxs203("div", { className: "datepicker-range-panels", children: [
7782
+ /* @__PURE__ */ jsx318(
7783
+ SingleDatePicker_default,
7784
+ {
7785
+ value: startDate,
7786
+ onChange: handleStartChange,
7787
+ minDate,
7788
+ maxDate: startMaxDate,
7789
+ rangeStart: startDate,
7790
+ rangeEnd: endDate,
7791
+ locale
7792
+ }
7793
+ ),
7794
+ /* @__PURE__ */ jsx318(
7795
+ SingleDatePicker_default,
7796
+ {
7797
+ value: endDate,
7798
+ onChange: handleEndChange,
7799
+ minDate: endMinDate,
7800
+ maxDate,
7801
+ rangeStart: startDate,
7802
+ rangeEnd: endDate,
7803
+ locale
7804
+ }
7805
+ )
7806
+ ] }),
7807
+ /* @__PURE__ */ jsx318("div", { className: "datepicker-range-mobile", children: activeTab === "start" ? /* @__PURE__ */ jsx318(
7808
+ SingleDatePicker_default,
7809
+ {
7810
+ value: startDate,
7811
+ onChange: handleStartChange,
7812
+ minDate,
7813
+ maxDate: startMaxDate,
7814
+ rangeStart: startDate,
7815
+ rangeEnd: endDate,
7816
+ locale
7817
+ }
7818
+ ) : /* @__PURE__ */ jsx318(
7819
+ SingleDatePicker_default,
7820
+ {
7821
+ value: endDate,
7822
+ onChange: handleEndChange,
7823
+ minDate: endMinDate,
7824
+ maxDate,
7825
+ rangeStart: startDate,
7826
+ rangeEnd: endDate,
7827
+ locale
7828
+ }
7829
+ ) })
7830
+ ]
7831
+ }
7832
+ );
7833
+ return /* @__PURE__ */ jsx318(Field, { label, required, labelId, validations, children: calendar });
7800
7834
  };
7801
7835
  RangePicker.displayName = "RangePicker";
7802
7836
  var RangePicker_default = RangePicker;
@@ -7945,8 +7979,10 @@ import React19 from "react";
7945
7979
  var useAutoPosition = (triggerRef, popRef, enabled = true) => {
7946
7980
  const [position, setPosition] = React19.useState({
7947
7981
  position: {},
7948
- direction: "bottom"
7982
+ direction: "bottom",
7983
+ repositioning: false
7949
7984
  });
7985
+ const placedRef = React19.useRef(false);
7950
7986
  const calculatePosition = React19.useCallback(() => {
7951
7987
  if (!triggerRef.current || !popRef.current) return;
7952
7988
  const triggerRect = triggerRef.current.getBoundingClientRect();
@@ -7971,11 +8007,16 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
7971
8007
  if (left < 8) left = 8;
7972
8008
  setPosition({
7973
8009
  position: { top, left },
7974
- direction
8010
+ direction,
8011
+ repositioning: placedRef.current
7975
8012
  });
8013
+ placedRef.current = true;
7976
8014
  }, [triggerRef, popRef]);
7977
8015
  React19.useLayoutEffect(() => {
7978
- if (!enabled) return;
8016
+ if (!enabled) {
8017
+ placedRef.current = false;
8018
+ return;
8019
+ }
7979
8020
  calculatePosition();
7980
8021
  const raf = requestAnimationFrame(calculatePosition);
7981
8022
  return () => cancelAnimationFrame(raf);
@@ -7990,11 +8031,17 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
7990
8031
  }, [calculatePosition, enabled, popRef]);
7991
8032
  React19.useEffect(() => {
7992
8033
  if (!enabled) return;
7993
- window.addEventListener("resize", calculatePosition);
7994
- window.addEventListener("scroll", calculatePosition, true);
8034
+ let raf = 0;
8035
+ const onScrollOrResize = () => {
8036
+ cancelAnimationFrame(raf);
8037
+ raf = requestAnimationFrame(calculatePosition);
8038
+ };
8039
+ window.addEventListener("resize", onScrollOrResize, { passive: true });
8040
+ window.addEventListener("scroll", onScrollOrResize, { passive: true, capture: true });
7995
8041
  return () => {
7996
- window.removeEventListener("resize", calculatePosition);
7997
- window.removeEventListener("scroll", calculatePosition, true);
8042
+ cancelAnimationFrame(raf);
8043
+ window.removeEventListener("resize", onScrollOrResize);
8044
+ window.removeEventListener("scroll", onScrollOrResize, true);
7998
8045
  };
7999
8046
  }, [calculatePosition, enabled]);
8000
8047
  return position;
@@ -8035,7 +8082,7 @@ var Dropdown = (props) => {
8035
8082
  const [visible, setVisible] = React21.useState(false);
8036
8083
  const triggerRef = React21.useRef(null);
8037
8084
  const menuRef = React21.useRef(null);
8038
- const { position, direction } = useAutoPosition_default(triggerRef, menuRef, mounted);
8085
+ const { position, direction, repositioning } = useAutoPosition_default(triggerRef, menuRef, mounted);
8039
8086
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
8040
8087
  useEscapeKey(isOpen, () => {
8041
8088
  setIsOpen(false);
@@ -8070,7 +8117,7 @@ var Dropdown = (props) => {
8070
8117
  "div",
8071
8118
  {
8072
8119
  ref: menuRef,
8073
- className: clsx_default("lib-xplat-dropdown-menu", direction, { visible }),
8120
+ className: clsx_default("lib-xplat-dropdown-menu", direction, { visible }, repositioning && "repositioning"),
8074
8121
  style: { top: position.top, left: position.left },
8075
8122
  role: "menu",
8076
8123
  children: items.map((item) => /* @__PURE__ */ jsx322(
@@ -8096,6 +8143,111 @@ var Dropdown_default = Dropdown;
8096
8143
 
8097
8144
  // src/components/Editor/Editor.tsx
8098
8145
  import React22 from "react";
8146
+
8147
+ // src/components/Editor/highlight.ts
8148
+ var CODE_LANGUAGES = [
8149
+ { value: "plain", label: "\uC5C6\uC74C" },
8150
+ { value: "javascript", label: "JavaScript" },
8151
+ { value: "typescript", label: "TypeScript" },
8152
+ { value: "json", label: "JSON" },
8153
+ { value: "html", label: "HTML" },
8154
+ { value: "css", label: "CSS" },
8155
+ { value: "python", label: "Python" },
8156
+ { value: "sql", label: "SQL" },
8157
+ { value: "bash", label: "Bash" }
8158
+ ];
8159
+ var KEYWORDS = {
8160
+ plain: [],
8161
+ 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(" "),
8162
+ 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(" "),
8163
+ json: ["true", "false", "null"],
8164
+ html: [],
8165
+ css: [],
8166
+ 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(" "),
8167
+ 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(" "),
8168
+ 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(" ")
8169
+ };
8170
+ var LINE_COMMENT = {
8171
+ javascript: "//",
8172
+ typescript: "//",
8173
+ css: "",
8174
+ python: "#",
8175
+ sql: "--",
8176
+ bash: "#"
8177
+ };
8178
+ var escape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
8179
+ var wrap = (cls, text) => `<span class="tok-${cls}">${escape(text)}</span>`;
8180
+ var highlight = (code, lang) => {
8181
+ if (lang === "plain" || !code) return escape(code);
8182
+ const keywords = new Set(KEYWORDS[lang]);
8183
+ const lineComment = LINE_COMMENT[lang];
8184
+ const caseInsensitive = lang === "sql";
8185
+ let out = "";
8186
+ let i = 0;
8187
+ while (i < code.length) {
8188
+ const rest = code.slice(i);
8189
+ if ((lang === "javascript" || lang === "typescript" || lang === "css") && rest.startsWith("/*")) {
8190
+ const end = code.indexOf("*/", i + 2);
8191
+ const stop = end === -1 ? code.length : end + 2;
8192
+ out += wrap("comment", code.slice(i, stop));
8193
+ i = stop;
8194
+ continue;
8195
+ }
8196
+ if (lineComment && rest.startsWith(lineComment)) {
8197
+ const nl = code.indexOf("\n", i);
8198
+ const stop = nl === -1 ? code.length : nl;
8199
+ out += wrap("comment", code.slice(i, stop));
8200
+ i = stop;
8201
+ continue;
8202
+ }
8203
+ const q = rest[0];
8204
+ if (q === '"' || q === "'" || q === "`") {
8205
+ let j = 1;
8206
+ while (j < rest.length) {
8207
+ if (rest[j] === "\\") {
8208
+ j += 2;
8209
+ continue;
8210
+ }
8211
+ if (rest[j] === q) {
8212
+ j += 1;
8213
+ break;
8214
+ }
8215
+ if (rest[j] === "\n" && q !== "`") break;
8216
+ j += 1;
8217
+ }
8218
+ out += wrap("string", rest.slice(0, j));
8219
+ i += j;
8220
+ continue;
8221
+ }
8222
+ const num = /^\d+(\.\d+)?/.exec(rest);
8223
+ if (num && !/[\w$]/.test(code[i - 1] ?? "")) {
8224
+ out += wrap("number", num[0]);
8225
+ i += num[0].length;
8226
+ continue;
8227
+ }
8228
+ const word = /^[A-Za-z_$][\w$]*/.exec(rest);
8229
+ if (word) {
8230
+ const w = word[0];
8231
+ const hit = caseInsensitive ? keywords.has(w.toUpperCase()) : keywords.has(w);
8232
+ out += hit ? wrap("keyword", w) : escape(w);
8233
+ i += w.length;
8234
+ continue;
8235
+ }
8236
+ if (lang === "html" && rest.startsWith("<")) {
8237
+ const tag = /^<\/?[A-Za-z][\w-]*/.exec(rest);
8238
+ if (tag) {
8239
+ out += wrap("keyword", tag[0]);
8240
+ i += tag[0].length;
8241
+ continue;
8242
+ }
8243
+ }
8244
+ out += escape(code[i]);
8245
+ i += 1;
8246
+ }
8247
+ return out;
8248
+ };
8249
+
8250
+ // src/components/Editor/Editor.tsx
8099
8251
  import { jsx as jsx323, jsxs as jsxs207 } from "react/jsx-runtime";
8100
8252
  var DEFAULT_TOOLBAR = [
8101
8253
  "bold",
@@ -8103,14 +8255,27 @@ var DEFAULT_TOOLBAR = [
8103
8255
  "underline",
8104
8256
  "strikethrough",
8105
8257
  "code",
8106
- "heading",
8258
+ "heading-1",
8259
+ "heading-2",
8260
+ "heading-3",
8107
8261
  "list",
8108
8262
  "ordered-list",
8109
8263
  "blockquote",
8110
8264
  "code-block",
8111
8265
  "link",
8112
8266
  "image",
8113
- "divider"
8267
+ "divider",
8268
+ "checklist",
8269
+ "table",
8270
+ "align-left",
8271
+ "align-center",
8272
+ "align-right",
8273
+ "highlight",
8274
+ "superscript",
8275
+ "subscript",
8276
+ "clear-format",
8277
+ "undo",
8278
+ "redo"
8114
8279
  ];
8115
8280
  var ALLOWED_TAGS = /* @__PURE__ */ new Set([
8116
8281
  "p",
@@ -8139,11 +8304,41 @@ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
8139
8304
  "img",
8140
8305
  "hr",
8141
8306
  "span",
8142
- "div"
8307
+ "div",
8308
+ "sub",
8309
+ "sup",
8310
+ "mark",
8311
+ "table",
8312
+ "thead",
8313
+ "tbody",
8314
+ "tr",
8315
+ "th",
8316
+ "td"
8143
8317
  ]);
8144
8318
  var ALLOWED_ATTRS = {
8145
8319
  a: ["href", "target", "rel"],
8146
- img: ["src", "alt"]
8320
+ img: ["src", "alt", "width"],
8321
+ // 정렬·체크리스트는 클래스로만 표현한다. style 을 열면 붙여넣기로 아무 CSS 나 들어온다
8322
+ p: ["class"],
8323
+ h1: ["class"],
8324
+ h2: ["class"],
8325
+ h3: ["class"],
8326
+ h4: ["class"],
8327
+ h5: ["class"],
8328
+ h6: ["class"],
8329
+ ul: ["class"],
8330
+ ol: ["class"],
8331
+ li: ["class", "data-checked"],
8332
+ blockquote: ["class"],
8333
+ div: ["class"],
8334
+ span: ["class"],
8335
+ pre: ["class"],
8336
+ code: ["class"],
8337
+ mark: ["class"],
8338
+ table: ["class"],
8339
+ td: ["class"],
8340
+ th: ["class"],
8341
+ tr: ["class"]
8147
8342
  };
8148
8343
  var DROP_WITH_CONTENT = /* @__PURE__ */ new Set([
8149
8344
  "script",
@@ -8196,7 +8391,9 @@ var escapeHtml = (text) => {
8196
8391
  };
8197
8392
  var SLASH_ITEMS = [
8198
8393
  { key: "paragraph", label: "\uB2E8\uB77D", hint: "\uAE30\uBCF8 \uD14D\uC2A4\uD2B8" },
8199
- { key: "heading", label: "\uC81C\uBAA9 1", hint: "\uD070 \uC81C\uBAA9" },
8394
+ { key: "heading-1", label: "\uC81C\uBAA9 1", hint: "\uAC00\uC7A5 \uD070 \uC81C\uBAA9" },
8395
+ { key: "heading-2", label: "\uC81C\uBAA9 2", hint: "" },
8396
+ { key: "heading-3", label: "\uC81C\uBAA9 3", hint: "" },
8200
8397
  { key: "list", label: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D", hint: "\u2022 \uD56D\uBAA9" },
8201
8398
  { key: "ordered-list", label: "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D", hint: "1. \uD56D\uBAA9" },
8202
8399
  { key: "blockquote", label: "\uC778\uC6A9", hint: "" },
@@ -8239,6 +8436,8 @@ var Editor = (props) => {
8239
8436
  el.innerHTML = sanitizeHtml(value || "");
8240
8437
  if (!el.firstChild) el.innerHTML = "<p><br></p>";
8241
8438
  lastEmittedRef.current = null;
8439
+ historyRef.current = [{ html: el.innerHTML, caret: 0 }];
8440
+ historyIdxRef.current = 0;
8242
8441
  updateEmpty();
8243
8442
  }, [value]);
8244
8443
  const updateEmpty = () => {
@@ -8283,6 +8482,17 @@ var Editor = (props) => {
8283
8482
  if (document.queryCommandState("insertOrderedList")) next.add("ordered-list");
8284
8483
  const block = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
8285
8484
  if (block) next.add(`block:${block}`);
8485
+ if (document.queryCommandState("superscript")) next.add("superscript");
8486
+ if (document.queryCommandState("subscript")) next.add("subscript");
8487
+ } catch {
8488
+ }
8489
+ try {
8490
+ const blockEl = currentBlock();
8491
+ if (blockEl?.classList.contains("align-center")) next.add("align:center");
8492
+ else if (blockEl?.classList.contains("align-right")) next.add("align:right");
8493
+ else if (blockEl) next.add("align:left");
8494
+ if (blockEl?.closest("mark")) next.add("highlight");
8495
+ if (blockEl?.closest("ul.checklist")) next.add("checklist");
8286
8496
  } catch {
8287
8497
  }
8288
8498
  setActiveFormats(next);
@@ -8296,6 +8506,122 @@ var Editor = (props) => {
8296
8506
  const setBlock = (tag) => {
8297
8507
  exec("formatBlock", tag);
8298
8508
  };
8509
+ const toggleBlock = (tag) => {
8510
+ const current = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
8511
+ setBlock(current === tag ? "p" : tag);
8512
+ };
8513
+ const historyRef = React22.useRef([]);
8514
+ const historyIdxRef = React22.useRef(-1);
8515
+ const lastPushAtRef = React22.useRef(0);
8516
+ const isRestoringRef = React22.useRef(false);
8517
+ const caretOffset = (rootEl) => {
8518
+ const el = rootEl ?? editorRef.current;
8519
+ const sel = window.getSelection();
8520
+ if (!el || !sel || sel.rangeCount === 0 || !el.contains(sel.anchorNode)) return 0;
8521
+ const range = sel.getRangeAt(0).cloneRange();
8522
+ range.selectNodeContents(el);
8523
+ range.setEnd(sel.getRangeAt(0).endContainer, sel.getRangeAt(0).endOffset);
8524
+ return range.toString().length;
8525
+ };
8526
+ const setCaretOffset = (offset, rootEl) => {
8527
+ const el = rootEl ?? editorRef.current;
8528
+ if (!el) return;
8529
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
8530
+ let remaining = offset;
8531
+ let node = null;
8532
+ while (node = walker.nextNode()) {
8533
+ const len = node.textContent?.length ?? 0;
8534
+ if (remaining <= len) {
8535
+ const range = document.createRange();
8536
+ range.setStart(node, remaining);
8537
+ range.collapse(true);
8538
+ const sel = window.getSelection();
8539
+ sel?.removeAllRanges();
8540
+ sel?.addRange(range);
8541
+ return;
8542
+ }
8543
+ remaining -= len;
8544
+ }
8545
+ placeCaretAtEnd(el);
8546
+ };
8547
+ const pushHistory = (force = false) => {
8548
+ const el = editorRef.current;
8549
+ if (!el || isRestoringRef.current) return;
8550
+ const html = el.innerHTML;
8551
+ const hist = historyRef.current;
8552
+ const cur = hist[historyIdxRef.current];
8553
+ if (cur && cur.html === html) return;
8554
+ const now = Date.now();
8555
+ const coalesce = !force && cur && now - lastPushAtRef.current < 500;
8556
+ lastPushAtRef.current = now;
8557
+ if (coalesce) {
8558
+ hist[historyIdxRef.current] = { html, caret: caretOffset() };
8559
+ return;
8560
+ }
8561
+ hist.length = historyIdxRef.current + 1;
8562
+ hist.push({ html, caret: caretOffset() });
8563
+ if (hist.length > 200) hist.shift();
8564
+ historyIdxRef.current = hist.length - 1;
8565
+ };
8566
+ const applySnapshot = (snap) => {
8567
+ const el = editorRef.current;
8568
+ if (!el) return;
8569
+ isRestoringRef.current = true;
8570
+ el.innerHTML = snap.html;
8571
+ setCaretOffset(snap.caret);
8572
+ isRestoringRef.current = false;
8573
+ lastEmittedRef.current = el.innerHTML;
8574
+ onChange?.(el.innerHTML);
8575
+ updateEmpty();
8576
+ updateActiveFormats();
8577
+ };
8578
+ const undo = () => {
8579
+ pushHistory(true);
8580
+ if (historyIdxRef.current <= 0) return;
8581
+ historyIdxRef.current -= 1;
8582
+ applySnapshot(historyRef.current[historyIdxRef.current]);
8583
+ };
8584
+ const redo = () => {
8585
+ if (historyIdxRef.current >= historyRef.current.length - 1) return;
8586
+ historyIdxRef.current += 1;
8587
+ applySnapshot(historyRef.current[historyIdxRef.current]);
8588
+ };
8589
+ const langOf = (pre) => {
8590
+ const cls = Array.from(pre.classList).find((c) => c.startsWith("lang-"));
8591
+ return cls?.slice(5) ?? "plain";
8592
+ };
8593
+ const [codeLang, setCodeLang] = React22.useState("plain");
8594
+ const highlightRafRef = React22.useRef(0);
8595
+ const rehighlight = (pre) => {
8596
+ if (composingRef.current) return;
8597
+ const code = pre.querySelector("code");
8598
+ if (!code) return;
8599
+ const lang = langOf(pre);
8600
+ const text = code.textContent || "";
8601
+ const next = highlight(text, lang);
8602
+ if (code.innerHTML === next) return;
8603
+ const caret = caretOffset(code);
8604
+ isRestoringRef.current = true;
8605
+ code.innerHTML = next;
8606
+ setCaretOffset(caret, code);
8607
+ isRestoringRef.current = false;
8608
+ };
8609
+ const scheduleHighlight = () => {
8610
+ const pre = closestPre();
8611
+ if (!pre) return;
8612
+ cancelAnimationFrame(highlightRafRef.current);
8613
+ highlightRafRef.current = requestAnimationFrame(() => rehighlight(pre));
8614
+ };
8615
+ const changeCodeLang = (lang) => {
8616
+ const pre = closestPre();
8617
+ if (!pre) return;
8618
+ pushHistory(true);
8619
+ Array.from(pre.classList).filter((c) => c.startsWith("lang-")).forEach((c) => pre.classList.remove(c));
8620
+ if (lang !== "plain") pre.classList.add(`lang-${lang}`);
8621
+ setCodeLang(lang);
8622
+ rehighlight(pre);
8623
+ handleInput();
8624
+ };
8299
8625
  const closestPre = () => {
8300
8626
  const sel = window.getSelection();
8301
8627
  const node = sel?.anchorNode;
@@ -8304,6 +8630,7 @@ var Editor = (props) => {
8304
8630
  return el?.closest("pre") ?? null;
8305
8631
  };
8306
8632
  const insertCodeBlock = () => {
8633
+ pushHistory(true);
8307
8634
  const el = editorRef.current;
8308
8635
  const sel = window.getSelection();
8309
8636
  if (!el || !sel || sel.rangeCount === 0) return;
@@ -8386,7 +8713,167 @@ var Editor = (props) => {
8386
8713
  sel.addRange(r2);
8387
8714
  editorRef.current?.focus();
8388
8715
  };
8716
+ const clearFormat = () => {
8717
+ exec("removeFormat");
8718
+ exec("unlink");
8719
+ setBlock("p");
8720
+ };
8721
+ const indentList = (outdent) => {
8722
+ exec(outdent ? "outdent" : "indent");
8723
+ };
8724
+ const inListItem = () => {
8725
+ const sel = window.getSelection();
8726
+ const node = sel?.anchorNode;
8727
+ if (!node || !editorRef.current?.contains(node)) return false;
8728
+ const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
8729
+ return !!el?.closest("li");
8730
+ };
8731
+ const currentBlock = () => {
8732
+ const sel = window.getSelection();
8733
+ const node = sel?.anchorNode;
8734
+ const root = editorRef.current;
8735
+ if (!node || !root?.contains(node)) return null;
8736
+ let el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
8737
+ while (el && el !== root && !/^(P|H[1-6]|LI|BLOCKQUOTE|PRE|DIV|TD|TH)$/.test(el.tagName)) {
8738
+ el = el.parentElement;
8739
+ }
8740
+ return el && el !== root ? el : null;
8741
+ };
8742
+ const setAlign = (align) => {
8743
+ const block = currentBlock();
8744
+ if (!block) return;
8745
+ pushHistory(true);
8746
+ block.classList.remove("align-left", "align-center", "align-right");
8747
+ if (align !== "left") block.classList.add(`align-${align}`);
8748
+ handleInput();
8749
+ };
8750
+ const toggleChecklist = () => {
8751
+ pushHistory(true);
8752
+ const block = currentBlock();
8753
+ const list = block?.closest("ul");
8754
+ if (list?.classList.contains("checklist")) {
8755
+ list.classList.remove("checklist");
8756
+ list.querySelectorAll("li").forEach((li) => li.removeAttribute("data-checked"));
8757
+ handleInput();
8758
+ return;
8759
+ }
8760
+ if (list) {
8761
+ list.classList.add("checklist");
8762
+ list.querySelectorAll("li").forEach((li) => li.setAttribute("data-checked", "false"));
8763
+ handleInput();
8764
+ return;
8765
+ }
8766
+ exec("insertUnorderedList");
8767
+ const made = currentBlock()?.closest("ul");
8768
+ if (made) {
8769
+ made.classList.add("checklist");
8770
+ made.querySelectorAll("li").forEach((li) => li.setAttribute("data-checked", "false"));
8771
+ }
8772
+ handleInput();
8773
+ };
8774
+ const handleEditorClick = (e) => {
8775
+ if (readOnly) return;
8776
+ const target = e.target;
8777
+ const li = target.closest?.("li");
8778
+ if (!li || !li.closest("ul.checklist")) return;
8779
+ const rect = li.getBoundingClientRect();
8780
+ if (e.clientX - rect.left > 20) return;
8781
+ e.preventDefault();
8782
+ pushHistory(true);
8783
+ li.setAttribute("data-checked", li.getAttribute("data-checked") === "true" ? "false" : "true");
8784
+ handleInput();
8785
+ };
8786
+ const toggleHighlight = () => {
8787
+ const sel = window.getSelection();
8788
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
8789
+ pushHistory(true);
8790
+ const range = sel.getRangeAt(0);
8791
+ const node = sel.anchorNode;
8792
+ const el = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement;
8793
+ const existing = el?.closest("mark");
8794
+ if (existing) {
8795
+ const parent = existing.parentNode;
8796
+ while (existing.firstChild) parent?.insertBefore(existing.firstChild, existing);
8797
+ parent?.removeChild(existing);
8798
+ handleInput();
8799
+ return;
8800
+ }
8801
+ const mark = document.createElement("mark");
8802
+ try {
8803
+ range.surroundContents(mark);
8804
+ } catch {
8805
+ const frag = range.extractContents();
8806
+ mark.appendChild(frag);
8807
+ range.insertNode(mark);
8808
+ }
8809
+ handleInput();
8810
+ };
8811
+ const insertTable = (cols = 3, rows = 2) => {
8812
+ const sel = window.getSelection();
8813
+ if (!sel || sel.rangeCount === 0) return;
8814
+ pushHistory(true);
8815
+ const table = document.createElement("table");
8816
+ const thead = document.createElement("thead");
8817
+ const headRow = document.createElement("tr");
8818
+ for (let c = 0; c < cols; c++) {
8819
+ const th = document.createElement("th");
8820
+ th.innerHTML = "<br>";
8821
+ headRow.appendChild(th);
8822
+ }
8823
+ thead.appendChild(headRow);
8824
+ table.appendChild(thead);
8825
+ const tbody = document.createElement("tbody");
8826
+ for (let r2 = 0; r2 < rows; r2++) {
8827
+ const tr = document.createElement("tr");
8828
+ for (let c = 0; c < cols; c++) {
8829
+ const td = document.createElement("td");
8830
+ td.innerHTML = "<br>";
8831
+ tr.appendChild(td);
8832
+ }
8833
+ tbody.appendChild(tr);
8834
+ }
8835
+ table.appendChild(tbody);
8836
+ const range = sel.getRangeAt(0);
8837
+ range.deleteContents();
8838
+ range.insertNode(table);
8839
+ ensureParagraphAfter(table);
8840
+ const first = table.querySelector("th");
8841
+ if (first) placeCaretAtEnd(first);
8842
+ handleInput();
8843
+ };
8844
+ const moveTableCell = (backwards) => {
8845
+ const block = currentBlock();
8846
+ const cell = block?.closest("td, th");
8847
+ if (!cell) return false;
8848
+ const table = cell.closest("table");
8849
+ if (!table) return false;
8850
+ const cells = Array.from(table.querySelectorAll("td, th"));
8851
+ const i = cells.indexOf(cell);
8852
+ const next = cells[i + (backwards ? -1 : 1)];
8853
+ if (next) {
8854
+ placeCaretAtEnd(next);
8855
+ return true;
8856
+ }
8857
+ if (!backwards) {
8858
+ pushHistory(true);
8859
+ const lastRow = table.querySelector("tbody")?.lastElementChild;
8860
+ if (lastRow) {
8861
+ const tr = document.createElement("tr");
8862
+ for (let c = 0; c < lastRow.children.length; c++) {
8863
+ const td = document.createElement("td");
8864
+ td.innerHTML = "<br>";
8865
+ tr.appendChild(td);
8866
+ }
8867
+ lastRow.after(tr);
8868
+ placeCaretAtEnd(tr.firstElementChild);
8869
+ handleInput();
8870
+ return true;
8871
+ }
8872
+ }
8873
+ return false;
8874
+ };
8389
8875
  const insertDivider = () => {
8876
+ pushHistory(true);
8390
8877
  exec("insertHorizontalRule");
8391
8878
  const el = editorRef.current;
8392
8879
  if (el && el.lastElementChild?.tagName === "HR") {
@@ -8479,8 +8966,14 @@ var Editor = (props) => {
8479
8966
  case "paragraph":
8480
8967
  setBlock("p");
8481
8968
  break;
8482
- case "heading":
8483
- setBlock("h2");
8969
+ case "heading-1":
8970
+ toggleBlock("h1");
8971
+ break;
8972
+ case "heading-2":
8973
+ toggleBlock("h2");
8974
+ break;
8975
+ case "heading-3":
8976
+ toggleBlock("h3");
8484
8977
  break;
8485
8978
  case "list":
8486
8979
  exec("insertUnorderedList");
@@ -8573,6 +9066,8 @@ var Editor = (props) => {
8573
9066
  if (!el) return;
8574
9067
  const html = el.innerHTML;
8575
9068
  lastEmittedRef.current = html;
9069
+ pushHistory();
9070
+ scheduleHighlight();
8576
9071
  onChange?.(html);
8577
9072
  updateEmpty();
8578
9073
  updateActiveFormats();
@@ -8626,6 +9121,15 @@ var Editor = (props) => {
8626
9121
  return;
8627
9122
  }
8628
9123
  }
9124
+ if (e.key === "Tab" && moveTableCell(e.shiftKey)) {
9125
+ e.preventDefault();
9126
+ return;
9127
+ }
9128
+ if (e.key === "Tab" && inListItem()) {
9129
+ e.preventDefault();
9130
+ indentList(e.shiftKey);
9131
+ return;
9132
+ }
8629
9133
  if (slashOpen) {
8630
9134
  if (e.key === "ArrowDown") {
8631
9135
  e.preventDefault();
@@ -8649,6 +9153,21 @@ var Editor = (props) => {
8649
9153
  return;
8650
9154
  }
8651
9155
  }
9156
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "z") {
9157
+ e.preventDefault();
9158
+ if (e.shiftKey) redo();
9159
+ else undo();
9160
+ return;
9161
+ }
9162
+ if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "v") {
9163
+ plainPasteRef.current = true;
9164
+ return;
9165
+ }
9166
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "y") {
9167
+ e.preventDefault();
9168
+ redo();
9169
+ return;
9170
+ }
8652
9171
  if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
8653
9172
  const k = e.key.toLowerCase();
8654
9173
  if (k === "b") {
@@ -8682,10 +9201,16 @@ var Editor = (props) => {
8682
9201
  }
8683
9202
  }
8684
9203
  };
9204
+ const plainPasteRef = React22.useRef(false);
8685
9205
  const handlePaste = (e) => {
8686
9206
  if (readOnly) return;
8687
9207
  e.preventDefault();
8688
9208
  const text = e.clipboardData.getData("text/plain");
9209
+ if (plainPasteRef.current) {
9210
+ plainPasteRef.current = false;
9211
+ insertTextAtCaret(text);
9212
+ return;
9213
+ }
8689
9214
  if (closestPre()) {
8690
9215
  insertTextAtCaret(text);
8691
9216
  return;
@@ -8712,9 +9237,11 @@ var Editor = (props) => {
8712
9237
  };
8713
9238
  const handleSelectionUpdate = () => {
8714
9239
  updateActiveFormats();
9240
+ const pre = closestPre();
9241
+ setCodeLang(pre ? langOf(pre) : "plain");
8715
9242
  };
8716
9243
  const onToolbarAction = (item) => {
8717
- if (closestPre() && item !== "code-block") return;
9244
+ if (closestPre() && !["code-block", "undo", "redo"].includes(item)) return;
8718
9245
  switch (item) {
8719
9246
  case "bold":
8720
9247
  exec("bold");
@@ -8743,8 +9270,14 @@ var Editor = (props) => {
8743
9270
  handleInput();
8744
9271
  break;
8745
9272
  }
8746
- case "heading":
8747
- setBlock("h2");
9273
+ case "heading-1":
9274
+ toggleBlock("h1");
9275
+ break;
9276
+ case "heading-2":
9277
+ toggleBlock("h2");
9278
+ break;
9279
+ case "heading-3":
9280
+ toggleBlock("h3");
8748
9281
  break;
8749
9282
  case "list":
8750
9283
  exec("insertUnorderedList");
@@ -8767,6 +9300,39 @@ var Editor = (props) => {
8767
9300
  case "divider":
8768
9301
  insertDivider();
8769
9302
  break;
9303
+ case "checklist":
9304
+ toggleChecklist();
9305
+ break;
9306
+ case "align-left":
9307
+ setAlign("left");
9308
+ break;
9309
+ case "align-center":
9310
+ setAlign("center");
9311
+ break;
9312
+ case "align-right":
9313
+ setAlign("right");
9314
+ break;
9315
+ case "highlight":
9316
+ toggleHighlight();
9317
+ break;
9318
+ case "superscript":
9319
+ exec("superscript");
9320
+ break;
9321
+ case "subscript":
9322
+ exec("subscript");
9323
+ break;
9324
+ case "table":
9325
+ insertTable();
9326
+ break;
9327
+ case "clear-format":
9328
+ clearFormat();
9329
+ break;
9330
+ case "undo":
9331
+ undo();
9332
+ break;
9333
+ case "redo":
9334
+ redo();
9335
+ break;
8770
9336
  }
8771
9337
  };
8772
9338
  return /* @__PURE__ */ jsxs207("div", { className: clsx_default("lib-xplat-editor", isFocused && "focused", readOnly && "readonly"), children: [
@@ -8775,7 +9341,10 @@ var Editor = (props) => {
8775
9341
  {
8776
9342
  items: toolbar,
8777
9343
  active: activeFormats,
8778
- onAction: onToolbarAction
9344
+ onAction: onToolbarAction,
9345
+ codeLang,
9346
+ inCodeBlock: activeFormats.has("block:pre"),
9347
+ onCodeLangChange: changeCodeLang
8779
9348
  }
8780
9349
  ),
8781
9350
  /* @__PURE__ */ jsxs207("div", { className: "lib-xplat-editor__content", style: { minHeight }, children: [
@@ -8793,6 +9362,7 @@ var Editor = (props) => {
8793
9362
  className: "lib-xplat-editor__editable",
8794
9363
  onInput: handleInput,
8795
9364
  onKeyDown: handleKeyDown,
9365
+ onClick: handleEditorClick,
8796
9366
  onKeyUp: handleSelectionUpdate,
8797
9367
  onMouseUp: handleSelectionUpdate,
8798
9368
  onPaste: handlePaste,
@@ -8834,14 +9404,27 @@ var TOOLBAR_LABEL = {
8834
9404
  underline: "U",
8835
9405
  strikethrough: "S",
8836
9406
  code: "<>",
8837
- heading: "H",
9407
+ "heading-1": "H1",
9408
+ "heading-2": "H2",
9409
+ "heading-3": "H3",
8838
9410
  list: "\u2022",
8839
9411
  "ordered-list": "1.",
8840
9412
  blockquote: "\u275D",
8841
9413
  "code-block": "[ ]",
8842
9414
  link: "\u{1F517}",
8843
9415
  image: "\u{1F5BC}",
8844
- divider: "\u2014"
9416
+ divider: "\u2014",
9417
+ checklist: "\u2611",
9418
+ "align-left": "\u21E4",
9419
+ "align-center": "\u2194",
9420
+ "align-right": "\u21E5",
9421
+ highlight: "\u{1F58D}",
9422
+ superscript: "x\xB2",
9423
+ subscript: "x\u2082",
9424
+ table: "\u25A6",
9425
+ "clear-format": "\u232B",
9426
+ undo: "\u21B6",
9427
+ redo: "\u21B7"
8845
9428
  };
8846
9429
  var TOOLBAR_TITLE = {
8847
9430
  bold: "\uAD75\uAC8C (\u2318B)",
@@ -8849,36 +9432,74 @@ var TOOLBAR_TITLE = {
8849
9432
  underline: "\uBC11\uC904 (\u2318U)",
8850
9433
  strikethrough: "\uCDE8\uC18C\uC120",
8851
9434
  code: "\uC778\uB77C\uC778 \uCF54\uB4DC",
8852
- heading: "\uC81C\uBAA9",
9435
+ "heading-1": "\uC81C\uBAA9 1",
9436
+ "heading-2": "\uC81C\uBAA9 2",
9437
+ "heading-3": "\uC81C\uBAA9 3",
8853
9438
  list: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D",
8854
9439
  "ordered-list": "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D",
8855
9440
  blockquote: "\uC778\uC6A9",
8856
9441
  "code-block": "\uCF54\uB4DC \uBE14\uB85D",
8857
9442
  link: "\uB9C1\uD06C (\u2318K)",
8858
9443
  image: "\uC774\uBBF8\uC9C0",
8859
- divider: "\uAD6C\uBD84\uC120"
9444
+ divider: "\uAD6C\uBD84\uC120",
9445
+ checklist: "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
9446
+ "align-left": "\uC67C\uCABD \uC815\uB82C",
9447
+ "align-center": "\uAC00\uC6B4\uB370 \uC815\uB82C",
9448
+ "align-right": "\uC624\uB978\uCABD \uC815\uB82C",
9449
+ highlight: "\uD615\uAD11\uD39C",
9450
+ superscript: "\uC704 \uCCA8\uC790",
9451
+ subscript: "\uC544\uB798 \uCCA8\uC790",
9452
+ table: "\uD45C \uC0BD\uC785",
9453
+ "clear-format": "\uC11C\uC2DD \uC9C0\uC6B0\uAE30",
9454
+ undo: "\uB418\uB3CC\uB9AC\uAE30 (\u2318Z)",
9455
+ redo: "\uB2E4\uC2DC\uD558\uAE30 (\u2318\u21E7Z)"
8860
9456
  };
8861
9457
  var isActive = (item, active) => {
8862
- if (item === "heading") return active.has("block:h1") || active.has("block:h2") || active.has("block:h3");
9458
+ if (item === "heading-1") return active.has("block:h1");
9459
+ if (item === "heading-2") return active.has("block:h2");
9460
+ if (item === "heading-3") return active.has("block:h3");
8863
9461
  if (item === "blockquote") return active.has("block:blockquote");
8864
9462
  if (item === "code-block") return active.has("block:pre");
9463
+ if (item === "align-left") return active.has("align:left");
9464
+ if (item === "align-center") return active.has("align:center");
9465
+ if (item === "align-right") return active.has("align:right");
8865
9466
  return active.has(item);
8866
9467
  };
8867
- var EditorToolbar = ({ items, active, onAction }) => {
8868
- return /* @__PURE__ */ jsx323("div", { className: "lib-xplat-editor__toolbar", children: items.map((item) => /* @__PURE__ */ jsx323(
8869
- "button",
8870
- {
8871
- type: "button",
8872
- className: clsx_default("lib-xplat-editor__toolbar-btn", isActive(item, active) && "active"),
8873
- title: TOOLBAR_TITLE[item],
8874
- onMouseDown: (e) => {
8875
- e.preventDefault();
8876
- onAction(item);
9468
+ var EditorToolbar = ({
9469
+ items,
9470
+ active,
9471
+ onAction,
9472
+ codeLang,
9473
+ inCodeBlock,
9474
+ onCodeLangChange
9475
+ }) => {
9476
+ return /* @__PURE__ */ jsxs207("div", { className: "lib-xplat-editor__toolbar", children: [
9477
+ items.map((item) => /* @__PURE__ */ jsx323(
9478
+ "button",
9479
+ {
9480
+ type: "button",
9481
+ className: clsx_default("lib-xplat-editor__toolbar-btn", isActive(item, active) && "active"),
9482
+ title: TOOLBAR_TITLE[item],
9483
+ onMouseDown: (e) => {
9484
+ e.preventDefault();
9485
+ onAction(item);
9486
+ },
9487
+ children: TOOLBAR_LABEL[item]
8877
9488
  },
8878
- children: TOOLBAR_LABEL[item]
8879
- },
8880
- item
8881
- )) });
9489
+ item
9490
+ )),
9491
+ inCodeBlock && /* @__PURE__ */ jsx323(
9492
+ "select",
9493
+ {
9494
+ className: "lib-xplat-editor__lang",
9495
+ value: codeLang,
9496
+ "aria-label": "\uCF54\uB4DC \uC5B8\uC5B4",
9497
+ onMouseDown: (e) => e.stopPropagation(),
9498
+ onChange: (e) => onCodeLangChange(e.target.value),
9499
+ children: CODE_LANGUAGES.map((l) => /* @__PURE__ */ jsx323("option", { value: l.value, children: l.label }, l.value))
9500
+ }
9501
+ )
9502
+ ] });
8882
9503
  };
8883
9504
  var SlashMenu = ({ position, items, activeIndex, onSelect, onClose }) => {
8884
9505
  React22.useEffect(() => {
@@ -8982,10 +9603,13 @@ var FileUpload = (props) => {
8982
9603
  multiple = false,
8983
9604
  maxSize,
8984
9605
  onChange,
8985
- label = "\uD30C\uC77C\uC744 \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \uD074\uB9AD\uD558\uC5EC \uC5C5\uB85C\uB4DC",
9606
+ placeholder = "\uD30C\uC77C\uC744 \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \uD074\uB9AD\uD558\uC5EC \uC5C5\uB85C\uB4DC",
8986
9607
  description,
8987
- validations
9608
+ validations,
9609
+ label,
9610
+ required
8988
9611
  } = props;
9612
+ const fieldId = React23.useId();
8989
9613
  const [isDragOver, setIsDragOver] = React23.useState(false);
8990
9614
  const inputRef = React23.useRef(null);
8991
9615
  const handleFiles = (fileList) => {
@@ -9030,6 +9654,7 @@ var FileUpload = (props) => {
9030
9654
  "input",
9031
9655
  {
9032
9656
  ref: inputRef,
9657
+ id: fieldId,
9033
9658
  type: "file",
9034
9659
  accept,
9035
9660
  multiple,
@@ -9038,12 +9663,12 @@ var FileUpload = (props) => {
9038
9663
  }
9039
9664
  ),
9040
9665
  /* @__PURE__ */ jsx325("div", { className: "upload-icon", children: /* @__PURE__ */ jsx325(UploadIcon_default, {}) }),
9041
- /* @__PURE__ */ jsx325("p", { className: "upload-label", children: label }),
9666
+ /* @__PURE__ */ jsx325("p", { className: "upload-label", children: placeholder }),
9042
9667
  description && /* @__PURE__ */ jsx325("p", { className: "upload-description", children: description })
9043
9668
  ]
9044
9669
  }
9045
9670
  );
9046
- return /* @__PURE__ */ jsx325(Field, { validations, children: control });
9671
+ return /* @__PURE__ */ jsx325(Field, { label, required, htmlFor: fieldId, validations, children: control });
9047
9672
  };
9048
9673
  FileUpload.displayName = "FileUpload";
9049
9674
  var FileUpload_default = FileUpload;
@@ -9186,7 +9811,8 @@ var HtmlTypeWriter_default = HtmlTypeWriter;
9186
9811
  import React26 from "react";
9187
9812
  import { jsx as jsx327, jsxs as jsxs210 } from "react/jsx-runtime";
9188
9813
  var ImageSelector = (props) => {
9189
- const { value, label, onChange, validations } = props;
9814
+ const { value, placeholder, onChange, validations, label, required } = props;
9815
+ const fieldId = React26.useId();
9190
9816
  const [previewUrl, setPreviewUrl] = React26.useState();
9191
9817
  React26.useEffect(() => {
9192
9818
  if (!value) {
@@ -9216,7 +9842,7 @@ var ImageSelector = (props) => {
9216
9842
  {
9217
9843
  type: "file",
9218
9844
  accept: "image/*",
9219
- id: "image-input",
9845
+ id: fieldId,
9220
9846
  onChange: handleFileChange,
9221
9847
  ref: inputRef,
9222
9848
  "aria-invalid": hasError(validations) || void 0
@@ -9228,10 +9854,10 @@ var ImageSelector = (props) => {
9228
9854
  ] }),
9229
9855
  /* @__PURE__ */ jsx327("div", { className: "content", children: previewUrl ? /* @__PURE__ */ jsx327("img", { src: previewUrl, alt: "preview" }) : /* @__PURE__ */ jsxs210("div", { className: "skeleton", onClick: handleOpenFileDialog, children: [
9230
9856
  /* @__PURE__ */ jsx327("div", { className: "icon-wrapper", children: /* @__PURE__ */ jsx327(ImageIcon_default, {}) }),
9231
- /* @__PURE__ */ jsx327("div", { className: "label", children: label || "\uC774\uBBF8\uC9C0 \uCD94\uAC00\uD558\uAE30" })
9857
+ /* @__PURE__ */ jsx327("div", { className: "label", children: placeholder || "\uC774\uBBF8\uC9C0 \uCD94\uAC00\uD558\uAE30" })
9232
9858
  ] }) })
9233
9859
  ] });
9234
- return /* @__PURE__ */ jsx327(Field, { validations, children: control });
9860
+ return /* @__PURE__ */ jsx327(Field, { label, required, htmlFor: fieldId, validations, children: control });
9235
9861
  };
9236
9862
  ImageSelector.displayName = "ImageSelector";
9237
9863
  var ImageSelector_default = ImageSelector;
@@ -9423,6 +10049,7 @@ var PopOver = (props) => {
9423
10049
  className: clsx_default(
9424
10050
  "lib-xplat-pop-over-content",
9425
10051
  position.direction,
10052
+ position.repositioning && "repositioning",
9426
10053
  eventTrigger && "visible"
9427
10054
  ),
9428
10055
  ref: popRef,
@@ -9769,7 +10396,7 @@ var SelectRoot = (props) => {
9769
10396
  mounted && /* @__PURE__ */ jsx337(Portal_default, { children: /* @__PURE__ */ jsxs216(
9770
10397
  "div",
9771
10398
  {
9772
- className: clsx_default("lib-xplat-select-content", size, position.direction, stateClass),
10399
+ className: clsx_default("lib-xplat-select-content", size, position.direction, stateClass, position.repositioning && "repositioning"),
9773
10400
  ref: contentRef,
9774
10401
  style: { ...position.position, width: triggerRef.current?.offsetWidth },
9775
10402
  role: "listbox",
@@ -10662,7 +11289,7 @@ var TimePicker = (props) => {
10662
11289
  "div",
10663
11290
  {
10664
11291
  ref: contentRef,
10665
- className: clsx_default("lib-xplat-time-picker-content", size, visible ? "enter" : "exit"),
11292
+ className: clsx_default("lib-xplat-time-picker-content", size, visible ? "enter" : "exit", position.repositioning && "repositioning"),
10666
11293
  style: { ...position.position, width: triggerRef.current?.offsetWidth },
10667
11294
  role: "listbox",
10668
11295
  children: options.map((t) => /* @__PURE__ */ jsx351(