@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
@@ -1633,11 +1633,11 @@ var FieldMessage = (props) => {
1633
1633
  FieldMessage.displayName = "FieldMessage";
1634
1634
  var FieldMessage_default = FieldMessage;
1635
1635
  var Field = (props) => {
1636
- const { label, required = false, htmlFor, validations, children } = props;
1636
+ const { label, required = false, htmlFor, labelId, validations, children } = props;
1637
1637
  const hasMessages = !!validations && validations.length > 0;
1638
1638
  if (!label && !hasMessages) return /* @__PURE__ */ (0, import_jsx_runtime297.jsx)(import_jsx_runtime297.Fragment, { children });
1639
1639
  return /* @__PURE__ */ (0, import_jsx_runtime297.jsxs)("div", { className: "lib-xplat-field", children: [
1640
- label && /* @__PURE__ */ (0, import_jsx_runtime297.jsxs)("label", { className: "lib-xplat-field-label", htmlFor, children: [
1640
+ label && /* @__PURE__ */ (0, import_jsx_runtime297.jsxs)("label", { className: "lib-xplat-field-label", id: labelId, htmlFor, children: [
1641
1641
  label,
1642
1642
  required && /* @__PURE__ */ (0, import_jsx_runtime297.jsx)("span", { className: "required", "aria-hidden": "true", children: "*" })
1643
1643
  ] }),
@@ -3404,8 +3404,12 @@ var SingleDatePicker = (props) => {
3404
3404
  highlightDates = [],
3405
3405
  locale = "ko",
3406
3406
  rangeStart,
3407
- rangeEnd
3407
+ rangeEnd,
3408
+ label,
3409
+ required,
3410
+ validations
3408
3411
  } = props;
3412
+ const labelId = import_react12.default.useId();
3409
3413
  const initialYear = value?.getFullYear();
3410
3414
  const initialMonth = value?.getMonth();
3411
3415
  const { year, month, days, goToPrevMonth, goToNextMonth, setYear, setMonth } = useCalendar(
@@ -3468,10 +3472,12 @@ var SingleDatePicker = (props) => {
3468
3472
  const monthLabels = MONTH_LABELS[locale];
3469
3473
  const titleText = pickerMode === "days" ? locale === "ko" ? `${year}\uB144 ${monthLabels[month]}` : `${monthLabels[month]} ${year}` : pickerMode === "months" ? `${year}` : `${yearRangeStart} - ${yearRangeStart + 11}`;
3470
3474
  const hasRange = rangeStart != null && rangeEnd != null;
3471
- return /* @__PURE__ */ (0, import_jsx_runtime314.jsxs)(
3475
+ const calendar = /* @__PURE__ */ (0, import_jsx_runtime314.jsxs)(
3472
3476
  "div",
3473
3477
  {
3474
3478
  className: clsx_default("lib-xplat-datepicker", "single"),
3479
+ role: "group",
3480
+ "aria-labelledby": label ? labelId : void 0,
3475
3481
  children: [
3476
3482
  /* @__PURE__ */ (0, import_jsx_runtime314.jsxs)("div", { className: "datepicker-header", children: [
3477
3483
  /* @__PURE__ */ (0, import_jsx_runtime314.jsx)("button", { className: "datepicker-nav", onClick: handlePrev, type: "button", children: /* @__PURE__ */ (0, import_jsx_runtime314.jsx)(ChevronLeftIcon_default, {}) }),
@@ -3492,18 +3498,18 @@ var SingleDatePicker = (props) => {
3492
3498
  y
3493
3499
  );
3494
3500
  }) }),
3495
- pickerMode === "months" && /* @__PURE__ */ (0, import_jsx_runtime314.jsx)("div", { className: "datepicker-picker-grid", children: monthLabels.map((label, i) => /* @__PURE__ */ (0, import_jsx_runtime314.jsx)(
3501
+ pickerMode === "months" && /* @__PURE__ */ (0, import_jsx_runtime314.jsx)("div", { className: "datepicker-picker-grid", children: monthLabels.map((label2, i) => /* @__PURE__ */ (0, import_jsx_runtime314.jsx)(
3496
3502
  "button",
3497
3503
  {
3498
3504
  type: "button",
3499
3505
  className: clsx_default("datepicker-picker-cell", i === month && "active"),
3500
3506
  onClick: () => handleMonthSelect(i),
3501
- children: label
3507
+ children: label2
3502
3508
  },
3503
3509
  i
3504
3510
  )) }),
3505
3511
  pickerMode === "days" && /* @__PURE__ */ (0, import_jsx_runtime314.jsxs)(import_jsx_runtime314.Fragment, { children: [
3506
- /* @__PURE__ */ (0, import_jsx_runtime314.jsx)("div", { className: "datepicker-weekdays", children: weekdays.map((label, i) => /* @__PURE__ */ (0, import_jsx_runtime314.jsx)(
3512
+ /* @__PURE__ */ (0, import_jsx_runtime314.jsx)("div", { className: "datepicker-weekdays", children: weekdays.map((label2, i) => /* @__PURE__ */ (0, import_jsx_runtime314.jsx)(
3507
3513
  "div",
3508
3514
  {
3509
3515
  className: clsx_default(
@@ -3511,9 +3517,9 @@ var SingleDatePicker = (props) => {
3511
3517
  i === 0 && "sunday",
3512
3518
  i === 6 && "saturday"
3513
3519
  ),
3514
- children: label
3520
+ children: label2
3515
3521
  },
3516
- label
3522
+ label2
3517
3523
  )) }),
3518
3524
  /* @__PURE__ */ (0, import_jsx_runtime314.jsx)("div", { className: "datepicker-grid", children: days.map((day, idx) => {
3519
3525
  const t = day.date.getTime();
@@ -3545,6 +3551,7 @@ var SingleDatePicker = (props) => {
3545
3551
  ]
3546
3552
  }
3547
3553
  );
3554
+ return /* @__PURE__ */ (0, import_jsx_runtime314.jsx)(Field, { label, required, labelId, validations, children: calendar });
3548
3555
  };
3549
3556
  SingleDatePicker.displayName = "SingleDatePicker";
3550
3557
  var SingleDatePicker_default = SingleDatePicker;
@@ -3559,7 +3566,18 @@ var formatDate = (date) => {
3559
3566
  return `${y}/${m}/${d}`;
3560
3567
  };
3561
3568
  var InputDatePicker = (props) => {
3562
- const { value, onChange, minDate, maxDate, disabled, locale = "ko", placeholder } = props;
3569
+ const {
3570
+ value,
3571
+ onChange,
3572
+ minDate,
3573
+ maxDate,
3574
+ disabled,
3575
+ locale = "ko",
3576
+ placeholder,
3577
+ label,
3578
+ required,
3579
+ validations
3580
+ } = props;
3563
3581
  const [isOpen, setIsOpen] = import_react13.default.useState(false);
3564
3582
  const [tempDate, setTempDate] = import_react13.default.useState(value ?? /* @__PURE__ */ new Date());
3565
3583
  const handleOpen = () => {
@@ -3585,6 +3603,9 @@ var InputDatePicker = (props) => {
3585
3603
  placeholder,
3586
3604
  suffix: /* @__PURE__ */ (0, import_jsx_runtime315.jsx)(CalenderIcon_default, {}),
3587
3605
  disabled,
3606
+ label,
3607
+ required,
3608
+ validations,
3588
3609
  readOnly: true
3589
3610
  }
3590
3611
  ) }),
@@ -3716,8 +3737,12 @@ var RangePicker = (props) => {
3716
3737
  onChange,
3717
3738
  minDate,
3718
3739
  maxDate,
3719
- locale = "ko"
3740
+ locale = "ko",
3741
+ label,
3742
+ required,
3743
+ validations
3720
3744
  } = props;
3745
+ const labelId = import_react16.default.useId();
3721
3746
  const [activeTab, setActiveTab] = import_react16.default.useState("start");
3722
3747
  const handleStartChange = (date) => {
3723
3748
  if (!date) return;
@@ -3731,70 +3756,79 @@ var RangePicker = (props) => {
3731
3756
  };
3732
3757
  const startMaxDate = maxDate && endDate < maxDate ? endDate : endDate;
3733
3758
  const endMinDate = minDate && startDate > minDate ? startDate : startDate;
3734
- return /* @__PURE__ */ (0, import_jsx_runtime318.jsxs)("div", { className: clsx_default("lib-xplat-datepicker", "range"), children: [
3735
- /* @__PURE__ */ (0, import_jsx_runtime318.jsx)("div", { className: "datepicker-range-tabs", children: /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(
3736
- Tab_default,
3737
- {
3738
- activeIndex: activeTab === "start" ? 0 : 1,
3739
- tabs: [
3740
- { value: "start", title: locale === "ko" ? "\uC2DC\uC791\uC77C" : "Start" },
3741
- { value: "end", title: locale === "ko" ? "\uC885\uB8CC\uC77C" : "End" }
3742
- ],
3743
- onChange: (tab) => setActiveTab(tab.value),
3744
- type: "toggle",
3745
- size: "sm"
3746
- }
3747
- ) }),
3748
- /* @__PURE__ */ (0, import_jsx_runtime318.jsxs)("div", { className: "datepicker-range-panels", children: [
3749
- /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(
3750
- SingleDatePicker_default,
3751
- {
3752
- value: startDate,
3753
- onChange: handleStartChange,
3754
- minDate,
3755
- maxDate: startMaxDate,
3756
- rangeStart: startDate,
3757
- rangeEnd: endDate,
3758
- locale
3759
- }
3760
- ),
3761
- /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(
3762
- SingleDatePicker_default,
3763
- {
3764
- value: endDate,
3765
- onChange: handleEndChange,
3766
- minDate: endMinDate,
3767
- maxDate,
3768
- rangeStart: startDate,
3769
- rangeEnd: endDate,
3770
- locale
3771
- }
3772
- )
3773
- ] }),
3774
- /* @__PURE__ */ (0, import_jsx_runtime318.jsx)("div", { className: "datepicker-range-mobile", children: activeTab === "start" ? /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(
3775
- SingleDatePicker_default,
3776
- {
3777
- value: startDate,
3778
- onChange: handleStartChange,
3779
- minDate,
3780
- maxDate: startMaxDate,
3781
- rangeStart: startDate,
3782
- rangeEnd: endDate,
3783
- locale
3784
- }
3785
- ) : /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(
3786
- SingleDatePicker_default,
3787
- {
3788
- value: endDate,
3789
- onChange: handleEndChange,
3790
- minDate: endMinDate,
3791
- maxDate,
3792
- rangeStart: startDate,
3793
- rangeEnd: endDate,
3794
- locale
3795
- }
3796
- ) })
3797
- ] });
3759
+ const calendar = /* @__PURE__ */ (0, import_jsx_runtime318.jsxs)(
3760
+ "div",
3761
+ {
3762
+ className: clsx_default("lib-xplat-datepicker", "range"),
3763
+ role: "group",
3764
+ "aria-labelledby": label ? labelId : void 0,
3765
+ children: [
3766
+ /* @__PURE__ */ (0, import_jsx_runtime318.jsx)("div", { className: "datepicker-range-tabs", children: /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(
3767
+ Tab_default,
3768
+ {
3769
+ activeIndex: activeTab === "start" ? 0 : 1,
3770
+ tabs: [
3771
+ { value: "start", title: locale === "ko" ? "\uC2DC\uC791\uC77C" : "Start" },
3772
+ { value: "end", title: locale === "ko" ? "\uC885\uB8CC\uC77C" : "End" }
3773
+ ],
3774
+ onChange: (tab) => setActiveTab(tab.value),
3775
+ type: "toggle",
3776
+ size: "sm"
3777
+ }
3778
+ ) }),
3779
+ /* @__PURE__ */ (0, import_jsx_runtime318.jsxs)("div", { className: "datepicker-range-panels", children: [
3780
+ /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(
3781
+ SingleDatePicker_default,
3782
+ {
3783
+ value: startDate,
3784
+ onChange: handleStartChange,
3785
+ minDate,
3786
+ maxDate: startMaxDate,
3787
+ rangeStart: startDate,
3788
+ rangeEnd: endDate,
3789
+ locale
3790
+ }
3791
+ ),
3792
+ /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(
3793
+ SingleDatePicker_default,
3794
+ {
3795
+ value: endDate,
3796
+ onChange: handleEndChange,
3797
+ minDate: endMinDate,
3798
+ maxDate,
3799
+ rangeStart: startDate,
3800
+ rangeEnd: endDate,
3801
+ locale
3802
+ }
3803
+ )
3804
+ ] }),
3805
+ /* @__PURE__ */ (0, import_jsx_runtime318.jsx)("div", { className: "datepicker-range-mobile", children: activeTab === "start" ? /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(
3806
+ SingleDatePicker_default,
3807
+ {
3808
+ value: startDate,
3809
+ onChange: handleStartChange,
3810
+ minDate,
3811
+ maxDate: startMaxDate,
3812
+ rangeStart: startDate,
3813
+ rangeEnd: endDate,
3814
+ locale
3815
+ }
3816
+ ) : /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(
3817
+ SingleDatePicker_default,
3818
+ {
3819
+ value: endDate,
3820
+ onChange: handleEndChange,
3821
+ minDate: endMinDate,
3822
+ maxDate,
3823
+ rangeStart: startDate,
3824
+ rangeEnd: endDate,
3825
+ locale
3826
+ }
3827
+ ) })
3828
+ ]
3829
+ }
3830
+ );
3831
+ return /* @__PURE__ */ (0, import_jsx_runtime318.jsx)(Field, { label, required, labelId, validations, children: calendar });
3798
3832
  };
3799
3833
  RangePicker.displayName = "RangePicker";
3800
3834
  var RangePicker_default = RangePicker;
@@ -3943,8 +3977,10 @@ var import_react19 = __toESM(require("react"), 1);
3943
3977
  var useAutoPosition = (triggerRef, popRef, enabled = true) => {
3944
3978
  const [position, setPosition] = import_react19.default.useState({
3945
3979
  position: {},
3946
- direction: "bottom"
3980
+ direction: "bottom",
3981
+ repositioning: false
3947
3982
  });
3983
+ const placedRef = import_react19.default.useRef(false);
3948
3984
  const calculatePosition = import_react19.default.useCallback(() => {
3949
3985
  if (!triggerRef.current || !popRef.current) return;
3950
3986
  const triggerRect = triggerRef.current.getBoundingClientRect();
@@ -3969,11 +4005,16 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
3969
4005
  if (left < 8) left = 8;
3970
4006
  setPosition({
3971
4007
  position: { top, left },
3972
- direction
4008
+ direction,
4009
+ repositioning: placedRef.current
3973
4010
  });
4011
+ placedRef.current = true;
3974
4012
  }, [triggerRef, popRef]);
3975
4013
  import_react19.default.useLayoutEffect(() => {
3976
- if (!enabled) return;
4014
+ if (!enabled) {
4015
+ placedRef.current = false;
4016
+ return;
4017
+ }
3977
4018
  calculatePosition();
3978
4019
  const raf = requestAnimationFrame(calculatePosition);
3979
4020
  return () => cancelAnimationFrame(raf);
@@ -3988,11 +4029,17 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
3988
4029
  }, [calculatePosition, enabled, popRef]);
3989
4030
  import_react19.default.useEffect(() => {
3990
4031
  if (!enabled) return;
3991
- window.addEventListener("resize", calculatePosition);
3992
- window.addEventListener("scroll", calculatePosition, true);
4032
+ let raf = 0;
4033
+ const onScrollOrResize = () => {
4034
+ cancelAnimationFrame(raf);
4035
+ raf = requestAnimationFrame(calculatePosition);
4036
+ };
4037
+ window.addEventListener("resize", onScrollOrResize, { passive: true });
4038
+ window.addEventListener("scroll", onScrollOrResize, { passive: true, capture: true });
3993
4039
  return () => {
3994
- window.removeEventListener("resize", calculatePosition);
3995
- window.removeEventListener("scroll", calculatePosition, true);
4040
+ cancelAnimationFrame(raf);
4041
+ window.removeEventListener("resize", onScrollOrResize);
4042
+ window.removeEventListener("scroll", onScrollOrResize, true);
3996
4043
  };
3997
4044
  }, [calculatePosition, enabled]);
3998
4045
  return position;
@@ -4033,7 +4080,7 @@ var Dropdown = (props) => {
4033
4080
  const [visible, setVisible] = import_react21.default.useState(false);
4034
4081
  const triggerRef = import_react21.default.useRef(null);
4035
4082
  const menuRef = import_react21.default.useRef(null);
4036
- const { position, direction } = useAutoPosition_default(triggerRef, menuRef, mounted);
4083
+ const { position, direction, repositioning } = useAutoPosition_default(triggerRef, menuRef, mounted);
4037
4084
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
4038
4085
  useEscapeKey(isOpen, () => {
4039
4086
  setIsOpen(false);
@@ -4068,7 +4115,7 @@ var Dropdown = (props) => {
4068
4115
  "div",
4069
4116
  {
4070
4117
  ref: menuRef,
4071
- className: clsx_default("lib-xplat-dropdown-menu", direction, { visible }),
4118
+ className: clsx_default("lib-xplat-dropdown-menu", direction, { visible }, repositioning && "repositioning"),
4072
4119
  style: { top: position.top, left: position.left },
4073
4120
  role: "menu",
4074
4121
  children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime322.jsx)(
@@ -4094,6 +4141,111 @@ var Dropdown_default = Dropdown;
4094
4141
 
4095
4142
  // src/components/Editor/Editor.tsx
4096
4143
  var import_react22 = __toESM(require("react"), 1);
4144
+
4145
+ // src/components/Editor/highlight.ts
4146
+ var CODE_LANGUAGES = [
4147
+ { value: "plain", label: "\uC5C6\uC74C" },
4148
+ { value: "javascript", label: "JavaScript" },
4149
+ { value: "typescript", label: "TypeScript" },
4150
+ { value: "json", label: "JSON" },
4151
+ { value: "html", label: "HTML" },
4152
+ { value: "css", label: "CSS" },
4153
+ { value: "python", label: "Python" },
4154
+ { value: "sql", label: "SQL" },
4155
+ { value: "bash", label: "Bash" }
4156
+ ];
4157
+ var KEYWORDS = {
4158
+ plain: [],
4159
+ 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(" "),
4160
+ 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(" "),
4161
+ json: ["true", "false", "null"],
4162
+ html: [],
4163
+ css: [],
4164
+ 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(" "),
4165
+ 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(" "),
4166
+ 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(" ")
4167
+ };
4168
+ var LINE_COMMENT = {
4169
+ javascript: "//",
4170
+ typescript: "//",
4171
+ css: "",
4172
+ python: "#",
4173
+ sql: "--",
4174
+ bash: "#"
4175
+ };
4176
+ var escape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4177
+ var wrap = (cls, text) => `<span class="tok-${cls}">${escape(text)}</span>`;
4178
+ var highlight = (code, lang) => {
4179
+ if (lang === "plain" || !code) return escape(code);
4180
+ const keywords = new Set(KEYWORDS[lang]);
4181
+ const lineComment = LINE_COMMENT[lang];
4182
+ const caseInsensitive = lang === "sql";
4183
+ let out = "";
4184
+ let i = 0;
4185
+ while (i < code.length) {
4186
+ const rest = code.slice(i);
4187
+ if ((lang === "javascript" || lang === "typescript" || lang === "css") && rest.startsWith("/*")) {
4188
+ const end = code.indexOf("*/", i + 2);
4189
+ const stop = end === -1 ? code.length : end + 2;
4190
+ out += wrap("comment", code.slice(i, stop));
4191
+ i = stop;
4192
+ continue;
4193
+ }
4194
+ if (lineComment && rest.startsWith(lineComment)) {
4195
+ const nl = code.indexOf("\n", i);
4196
+ const stop = nl === -1 ? code.length : nl;
4197
+ out += wrap("comment", code.slice(i, stop));
4198
+ i = stop;
4199
+ continue;
4200
+ }
4201
+ const q = rest[0];
4202
+ if (q === '"' || q === "'" || q === "`") {
4203
+ let j = 1;
4204
+ while (j < rest.length) {
4205
+ if (rest[j] === "\\") {
4206
+ j += 2;
4207
+ continue;
4208
+ }
4209
+ if (rest[j] === q) {
4210
+ j += 1;
4211
+ break;
4212
+ }
4213
+ if (rest[j] === "\n" && q !== "`") break;
4214
+ j += 1;
4215
+ }
4216
+ out += wrap("string", rest.slice(0, j));
4217
+ i += j;
4218
+ continue;
4219
+ }
4220
+ const num = /^\d+(\.\d+)?/.exec(rest);
4221
+ if (num && !/[\w$]/.test(code[i - 1] ?? "")) {
4222
+ out += wrap("number", num[0]);
4223
+ i += num[0].length;
4224
+ continue;
4225
+ }
4226
+ const word = /^[A-Za-z_$][\w$]*/.exec(rest);
4227
+ if (word) {
4228
+ const w = word[0];
4229
+ const hit = caseInsensitive ? keywords.has(w.toUpperCase()) : keywords.has(w);
4230
+ out += hit ? wrap("keyword", w) : escape(w);
4231
+ i += w.length;
4232
+ continue;
4233
+ }
4234
+ if (lang === "html" && rest.startsWith("<")) {
4235
+ const tag = /^<\/?[A-Za-z][\w-]*/.exec(rest);
4236
+ if (tag) {
4237
+ out += wrap("keyword", tag[0]);
4238
+ i += tag[0].length;
4239
+ continue;
4240
+ }
4241
+ }
4242
+ out += escape(code[i]);
4243
+ i += 1;
4244
+ }
4245
+ return out;
4246
+ };
4247
+
4248
+ // src/components/Editor/Editor.tsx
4097
4249
  var import_jsx_runtime323 = require("react/jsx-runtime");
4098
4250
  var DEFAULT_TOOLBAR = [
4099
4251
  "bold",
@@ -4101,14 +4253,27 @@ var DEFAULT_TOOLBAR = [
4101
4253
  "underline",
4102
4254
  "strikethrough",
4103
4255
  "code",
4104
- "heading",
4256
+ "heading-1",
4257
+ "heading-2",
4258
+ "heading-3",
4105
4259
  "list",
4106
4260
  "ordered-list",
4107
4261
  "blockquote",
4108
4262
  "code-block",
4109
4263
  "link",
4110
4264
  "image",
4111
- "divider"
4265
+ "divider",
4266
+ "checklist",
4267
+ "table",
4268
+ "align-left",
4269
+ "align-center",
4270
+ "align-right",
4271
+ "highlight",
4272
+ "superscript",
4273
+ "subscript",
4274
+ "clear-format",
4275
+ "undo",
4276
+ "redo"
4112
4277
  ];
4113
4278
  var ALLOWED_TAGS = /* @__PURE__ */ new Set([
4114
4279
  "p",
@@ -4137,11 +4302,41 @@ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
4137
4302
  "img",
4138
4303
  "hr",
4139
4304
  "span",
4140
- "div"
4305
+ "div",
4306
+ "sub",
4307
+ "sup",
4308
+ "mark",
4309
+ "table",
4310
+ "thead",
4311
+ "tbody",
4312
+ "tr",
4313
+ "th",
4314
+ "td"
4141
4315
  ]);
4142
4316
  var ALLOWED_ATTRS = {
4143
4317
  a: ["href", "target", "rel"],
4144
- img: ["src", "alt"]
4318
+ img: ["src", "alt", "width"],
4319
+ // 정렬·체크리스트는 클래스로만 표현한다. style 을 열면 붙여넣기로 아무 CSS 나 들어온다
4320
+ p: ["class"],
4321
+ h1: ["class"],
4322
+ h2: ["class"],
4323
+ h3: ["class"],
4324
+ h4: ["class"],
4325
+ h5: ["class"],
4326
+ h6: ["class"],
4327
+ ul: ["class"],
4328
+ ol: ["class"],
4329
+ li: ["class", "data-checked"],
4330
+ blockquote: ["class"],
4331
+ div: ["class"],
4332
+ span: ["class"],
4333
+ pre: ["class"],
4334
+ code: ["class"],
4335
+ mark: ["class"],
4336
+ table: ["class"],
4337
+ td: ["class"],
4338
+ th: ["class"],
4339
+ tr: ["class"]
4145
4340
  };
4146
4341
  var DROP_WITH_CONTENT = /* @__PURE__ */ new Set([
4147
4342
  "script",
@@ -4194,7 +4389,9 @@ var escapeHtml = (text) => {
4194
4389
  };
4195
4390
  var SLASH_ITEMS = [
4196
4391
  { key: "paragraph", label: "\uB2E8\uB77D", hint: "\uAE30\uBCF8 \uD14D\uC2A4\uD2B8" },
4197
- { key: "heading", label: "\uC81C\uBAA9 1", hint: "\uD070 \uC81C\uBAA9" },
4392
+ { key: "heading-1", label: "\uC81C\uBAA9 1", hint: "\uAC00\uC7A5 \uD070 \uC81C\uBAA9" },
4393
+ { key: "heading-2", label: "\uC81C\uBAA9 2", hint: "" },
4394
+ { key: "heading-3", label: "\uC81C\uBAA9 3", hint: "" },
4198
4395
  { key: "list", label: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D", hint: "\u2022 \uD56D\uBAA9" },
4199
4396
  { key: "ordered-list", label: "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D", hint: "1. \uD56D\uBAA9" },
4200
4397
  { key: "blockquote", label: "\uC778\uC6A9", hint: "" },
@@ -4237,6 +4434,8 @@ var Editor = (props) => {
4237
4434
  el.innerHTML = sanitizeHtml(value || "");
4238
4435
  if (!el.firstChild) el.innerHTML = "<p><br></p>";
4239
4436
  lastEmittedRef.current = null;
4437
+ historyRef.current = [{ html: el.innerHTML, caret: 0 }];
4438
+ historyIdxRef.current = 0;
4240
4439
  updateEmpty();
4241
4440
  }, [value]);
4242
4441
  const updateEmpty = () => {
@@ -4281,6 +4480,17 @@ var Editor = (props) => {
4281
4480
  if (document.queryCommandState("insertOrderedList")) next.add("ordered-list");
4282
4481
  const block = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
4283
4482
  if (block) next.add(`block:${block}`);
4483
+ if (document.queryCommandState("superscript")) next.add("superscript");
4484
+ if (document.queryCommandState("subscript")) next.add("subscript");
4485
+ } catch {
4486
+ }
4487
+ try {
4488
+ const blockEl = currentBlock();
4489
+ if (blockEl?.classList.contains("align-center")) next.add("align:center");
4490
+ else if (blockEl?.classList.contains("align-right")) next.add("align:right");
4491
+ else if (blockEl) next.add("align:left");
4492
+ if (blockEl?.closest("mark")) next.add("highlight");
4493
+ if (blockEl?.closest("ul.checklist")) next.add("checklist");
4284
4494
  } catch {
4285
4495
  }
4286
4496
  setActiveFormats(next);
@@ -4294,6 +4504,122 @@ var Editor = (props) => {
4294
4504
  const setBlock = (tag) => {
4295
4505
  exec("formatBlock", tag);
4296
4506
  };
4507
+ const toggleBlock = (tag) => {
4508
+ const current = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
4509
+ setBlock(current === tag ? "p" : tag);
4510
+ };
4511
+ const historyRef = import_react22.default.useRef([]);
4512
+ const historyIdxRef = import_react22.default.useRef(-1);
4513
+ const lastPushAtRef = import_react22.default.useRef(0);
4514
+ const isRestoringRef = import_react22.default.useRef(false);
4515
+ const caretOffset = (rootEl) => {
4516
+ const el = rootEl ?? editorRef.current;
4517
+ const sel = window.getSelection();
4518
+ if (!el || !sel || sel.rangeCount === 0 || !el.contains(sel.anchorNode)) return 0;
4519
+ const range = sel.getRangeAt(0).cloneRange();
4520
+ range.selectNodeContents(el);
4521
+ range.setEnd(sel.getRangeAt(0).endContainer, sel.getRangeAt(0).endOffset);
4522
+ return range.toString().length;
4523
+ };
4524
+ const setCaretOffset = (offset, rootEl) => {
4525
+ const el = rootEl ?? editorRef.current;
4526
+ if (!el) return;
4527
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
4528
+ let remaining = offset;
4529
+ let node = null;
4530
+ while (node = walker.nextNode()) {
4531
+ const len = node.textContent?.length ?? 0;
4532
+ if (remaining <= len) {
4533
+ const range = document.createRange();
4534
+ range.setStart(node, remaining);
4535
+ range.collapse(true);
4536
+ const sel = window.getSelection();
4537
+ sel?.removeAllRanges();
4538
+ sel?.addRange(range);
4539
+ return;
4540
+ }
4541
+ remaining -= len;
4542
+ }
4543
+ placeCaretAtEnd(el);
4544
+ };
4545
+ const pushHistory = (force = false) => {
4546
+ const el = editorRef.current;
4547
+ if (!el || isRestoringRef.current) return;
4548
+ const html = el.innerHTML;
4549
+ const hist = historyRef.current;
4550
+ const cur = hist[historyIdxRef.current];
4551
+ if (cur && cur.html === html) return;
4552
+ const now = Date.now();
4553
+ const coalesce = !force && cur && now - lastPushAtRef.current < 500;
4554
+ lastPushAtRef.current = now;
4555
+ if (coalesce) {
4556
+ hist[historyIdxRef.current] = { html, caret: caretOffset() };
4557
+ return;
4558
+ }
4559
+ hist.length = historyIdxRef.current + 1;
4560
+ hist.push({ html, caret: caretOffset() });
4561
+ if (hist.length > 200) hist.shift();
4562
+ historyIdxRef.current = hist.length - 1;
4563
+ };
4564
+ const applySnapshot = (snap) => {
4565
+ const el = editorRef.current;
4566
+ if (!el) return;
4567
+ isRestoringRef.current = true;
4568
+ el.innerHTML = snap.html;
4569
+ setCaretOffset(snap.caret);
4570
+ isRestoringRef.current = false;
4571
+ lastEmittedRef.current = el.innerHTML;
4572
+ onChange?.(el.innerHTML);
4573
+ updateEmpty();
4574
+ updateActiveFormats();
4575
+ };
4576
+ const undo = () => {
4577
+ pushHistory(true);
4578
+ if (historyIdxRef.current <= 0) return;
4579
+ historyIdxRef.current -= 1;
4580
+ applySnapshot(historyRef.current[historyIdxRef.current]);
4581
+ };
4582
+ const redo = () => {
4583
+ if (historyIdxRef.current >= historyRef.current.length - 1) return;
4584
+ historyIdxRef.current += 1;
4585
+ applySnapshot(historyRef.current[historyIdxRef.current]);
4586
+ };
4587
+ const langOf = (pre) => {
4588
+ const cls = Array.from(pre.classList).find((c) => c.startsWith("lang-"));
4589
+ return cls?.slice(5) ?? "plain";
4590
+ };
4591
+ const [codeLang, setCodeLang] = import_react22.default.useState("plain");
4592
+ const highlightRafRef = import_react22.default.useRef(0);
4593
+ const rehighlight = (pre) => {
4594
+ if (composingRef.current) return;
4595
+ const code = pre.querySelector("code");
4596
+ if (!code) return;
4597
+ const lang = langOf(pre);
4598
+ const text = code.textContent || "";
4599
+ const next = highlight(text, lang);
4600
+ if (code.innerHTML === next) return;
4601
+ const caret = caretOffset(code);
4602
+ isRestoringRef.current = true;
4603
+ code.innerHTML = next;
4604
+ setCaretOffset(caret, code);
4605
+ isRestoringRef.current = false;
4606
+ };
4607
+ const scheduleHighlight = () => {
4608
+ const pre = closestPre();
4609
+ if (!pre) return;
4610
+ cancelAnimationFrame(highlightRafRef.current);
4611
+ highlightRafRef.current = requestAnimationFrame(() => rehighlight(pre));
4612
+ };
4613
+ const changeCodeLang = (lang) => {
4614
+ const pre = closestPre();
4615
+ if (!pre) return;
4616
+ pushHistory(true);
4617
+ Array.from(pre.classList).filter((c) => c.startsWith("lang-")).forEach((c) => pre.classList.remove(c));
4618
+ if (lang !== "plain") pre.classList.add(`lang-${lang}`);
4619
+ setCodeLang(lang);
4620
+ rehighlight(pre);
4621
+ handleInput();
4622
+ };
4297
4623
  const closestPre = () => {
4298
4624
  const sel = window.getSelection();
4299
4625
  const node = sel?.anchorNode;
@@ -4302,6 +4628,7 @@ var Editor = (props) => {
4302
4628
  return el?.closest("pre") ?? null;
4303
4629
  };
4304
4630
  const insertCodeBlock = () => {
4631
+ pushHistory(true);
4305
4632
  const el = editorRef.current;
4306
4633
  const sel = window.getSelection();
4307
4634
  if (!el || !sel || sel.rangeCount === 0) return;
@@ -4384,7 +4711,167 @@ var Editor = (props) => {
4384
4711
  sel.addRange(r2);
4385
4712
  editorRef.current?.focus();
4386
4713
  };
4714
+ const clearFormat = () => {
4715
+ exec("removeFormat");
4716
+ exec("unlink");
4717
+ setBlock("p");
4718
+ };
4719
+ const indentList = (outdent) => {
4720
+ exec(outdent ? "outdent" : "indent");
4721
+ };
4722
+ const inListItem = () => {
4723
+ const sel = window.getSelection();
4724
+ const node = sel?.anchorNode;
4725
+ if (!node || !editorRef.current?.contains(node)) return false;
4726
+ const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
4727
+ return !!el?.closest("li");
4728
+ };
4729
+ const currentBlock = () => {
4730
+ const sel = window.getSelection();
4731
+ const node = sel?.anchorNode;
4732
+ const root = editorRef.current;
4733
+ if (!node || !root?.contains(node)) return null;
4734
+ let el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
4735
+ while (el && el !== root && !/^(P|H[1-6]|LI|BLOCKQUOTE|PRE|DIV|TD|TH)$/.test(el.tagName)) {
4736
+ el = el.parentElement;
4737
+ }
4738
+ return el && el !== root ? el : null;
4739
+ };
4740
+ const setAlign = (align) => {
4741
+ const block = currentBlock();
4742
+ if (!block) return;
4743
+ pushHistory(true);
4744
+ block.classList.remove("align-left", "align-center", "align-right");
4745
+ if (align !== "left") block.classList.add(`align-${align}`);
4746
+ handleInput();
4747
+ };
4748
+ const toggleChecklist = () => {
4749
+ pushHistory(true);
4750
+ const block = currentBlock();
4751
+ const list = block?.closest("ul");
4752
+ if (list?.classList.contains("checklist")) {
4753
+ list.classList.remove("checklist");
4754
+ list.querySelectorAll("li").forEach((li) => li.removeAttribute("data-checked"));
4755
+ handleInput();
4756
+ return;
4757
+ }
4758
+ if (list) {
4759
+ list.classList.add("checklist");
4760
+ list.querySelectorAll("li").forEach((li) => li.setAttribute("data-checked", "false"));
4761
+ handleInput();
4762
+ return;
4763
+ }
4764
+ exec("insertUnorderedList");
4765
+ const made = currentBlock()?.closest("ul");
4766
+ if (made) {
4767
+ made.classList.add("checklist");
4768
+ made.querySelectorAll("li").forEach((li) => li.setAttribute("data-checked", "false"));
4769
+ }
4770
+ handleInput();
4771
+ };
4772
+ const handleEditorClick = (e) => {
4773
+ if (readOnly) return;
4774
+ const target = e.target;
4775
+ const li = target.closest?.("li");
4776
+ if (!li || !li.closest("ul.checklist")) return;
4777
+ const rect = li.getBoundingClientRect();
4778
+ if (e.clientX - rect.left > 20) return;
4779
+ e.preventDefault();
4780
+ pushHistory(true);
4781
+ li.setAttribute("data-checked", li.getAttribute("data-checked") === "true" ? "false" : "true");
4782
+ handleInput();
4783
+ };
4784
+ const toggleHighlight = () => {
4785
+ const sel = window.getSelection();
4786
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
4787
+ pushHistory(true);
4788
+ const range = sel.getRangeAt(0);
4789
+ const node = sel.anchorNode;
4790
+ const el = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement;
4791
+ const existing = el?.closest("mark");
4792
+ if (existing) {
4793
+ const parent = existing.parentNode;
4794
+ while (existing.firstChild) parent?.insertBefore(existing.firstChild, existing);
4795
+ parent?.removeChild(existing);
4796
+ handleInput();
4797
+ return;
4798
+ }
4799
+ const mark = document.createElement("mark");
4800
+ try {
4801
+ range.surroundContents(mark);
4802
+ } catch {
4803
+ const frag = range.extractContents();
4804
+ mark.appendChild(frag);
4805
+ range.insertNode(mark);
4806
+ }
4807
+ handleInput();
4808
+ };
4809
+ const insertTable = (cols = 3, rows = 2) => {
4810
+ const sel = window.getSelection();
4811
+ if (!sel || sel.rangeCount === 0) return;
4812
+ pushHistory(true);
4813
+ const table = document.createElement("table");
4814
+ const thead = document.createElement("thead");
4815
+ const headRow = document.createElement("tr");
4816
+ for (let c = 0; c < cols; c++) {
4817
+ const th = document.createElement("th");
4818
+ th.innerHTML = "<br>";
4819
+ headRow.appendChild(th);
4820
+ }
4821
+ thead.appendChild(headRow);
4822
+ table.appendChild(thead);
4823
+ const tbody = document.createElement("tbody");
4824
+ for (let r2 = 0; r2 < rows; r2++) {
4825
+ const tr = document.createElement("tr");
4826
+ for (let c = 0; c < cols; c++) {
4827
+ const td = document.createElement("td");
4828
+ td.innerHTML = "<br>";
4829
+ tr.appendChild(td);
4830
+ }
4831
+ tbody.appendChild(tr);
4832
+ }
4833
+ table.appendChild(tbody);
4834
+ const range = sel.getRangeAt(0);
4835
+ range.deleteContents();
4836
+ range.insertNode(table);
4837
+ ensureParagraphAfter(table);
4838
+ const first = table.querySelector("th");
4839
+ if (first) placeCaretAtEnd(first);
4840
+ handleInput();
4841
+ };
4842
+ const moveTableCell = (backwards) => {
4843
+ const block = currentBlock();
4844
+ const cell = block?.closest("td, th");
4845
+ if (!cell) return false;
4846
+ const table = cell.closest("table");
4847
+ if (!table) return false;
4848
+ const cells = Array.from(table.querySelectorAll("td, th"));
4849
+ const i = cells.indexOf(cell);
4850
+ const next = cells[i + (backwards ? -1 : 1)];
4851
+ if (next) {
4852
+ placeCaretAtEnd(next);
4853
+ return true;
4854
+ }
4855
+ if (!backwards) {
4856
+ pushHistory(true);
4857
+ const lastRow = table.querySelector("tbody")?.lastElementChild;
4858
+ if (lastRow) {
4859
+ const tr = document.createElement("tr");
4860
+ for (let c = 0; c < lastRow.children.length; c++) {
4861
+ const td = document.createElement("td");
4862
+ td.innerHTML = "<br>";
4863
+ tr.appendChild(td);
4864
+ }
4865
+ lastRow.after(tr);
4866
+ placeCaretAtEnd(tr.firstElementChild);
4867
+ handleInput();
4868
+ return true;
4869
+ }
4870
+ }
4871
+ return false;
4872
+ };
4387
4873
  const insertDivider = () => {
4874
+ pushHistory(true);
4388
4875
  exec("insertHorizontalRule");
4389
4876
  const el = editorRef.current;
4390
4877
  if (el && el.lastElementChild?.tagName === "HR") {
@@ -4477,8 +4964,14 @@ var Editor = (props) => {
4477
4964
  case "paragraph":
4478
4965
  setBlock("p");
4479
4966
  break;
4480
- case "heading":
4481
- setBlock("h2");
4967
+ case "heading-1":
4968
+ toggleBlock("h1");
4969
+ break;
4970
+ case "heading-2":
4971
+ toggleBlock("h2");
4972
+ break;
4973
+ case "heading-3":
4974
+ toggleBlock("h3");
4482
4975
  break;
4483
4976
  case "list":
4484
4977
  exec("insertUnorderedList");
@@ -4571,6 +5064,8 @@ var Editor = (props) => {
4571
5064
  if (!el) return;
4572
5065
  const html = el.innerHTML;
4573
5066
  lastEmittedRef.current = html;
5067
+ pushHistory();
5068
+ scheduleHighlight();
4574
5069
  onChange?.(html);
4575
5070
  updateEmpty();
4576
5071
  updateActiveFormats();
@@ -4624,6 +5119,15 @@ var Editor = (props) => {
4624
5119
  return;
4625
5120
  }
4626
5121
  }
5122
+ if (e.key === "Tab" && moveTableCell(e.shiftKey)) {
5123
+ e.preventDefault();
5124
+ return;
5125
+ }
5126
+ if (e.key === "Tab" && inListItem()) {
5127
+ e.preventDefault();
5128
+ indentList(e.shiftKey);
5129
+ return;
5130
+ }
4627
5131
  if (slashOpen) {
4628
5132
  if (e.key === "ArrowDown") {
4629
5133
  e.preventDefault();
@@ -4647,6 +5151,21 @@ var Editor = (props) => {
4647
5151
  return;
4648
5152
  }
4649
5153
  }
5154
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "z") {
5155
+ e.preventDefault();
5156
+ if (e.shiftKey) redo();
5157
+ else undo();
5158
+ return;
5159
+ }
5160
+ if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "v") {
5161
+ plainPasteRef.current = true;
5162
+ return;
5163
+ }
5164
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "y") {
5165
+ e.preventDefault();
5166
+ redo();
5167
+ return;
5168
+ }
4650
5169
  if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
4651
5170
  const k = e.key.toLowerCase();
4652
5171
  if (k === "b") {
@@ -4680,10 +5199,16 @@ var Editor = (props) => {
4680
5199
  }
4681
5200
  }
4682
5201
  };
5202
+ const plainPasteRef = import_react22.default.useRef(false);
4683
5203
  const handlePaste = (e) => {
4684
5204
  if (readOnly) return;
4685
5205
  e.preventDefault();
4686
5206
  const text = e.clipboardData.getData("text/plain");
5207
+ if (plainPasteRef.current) {
5208
+ plainPasteRef.current = false;
5209
+ insertTextAtCaret(text);
5210
+ return;
5211
+ }
4687
5212
  if (closestPre()) {
4688
5213
  insertTextAtCaret(text);
4689
5214
  return;
@@ -4710,9 +5235,11 @@ var Editor = (props) => {
4710
5235
  };
4711
5236
  const handleSelectionUpdate = () => {
4712
5237
  updateActiveFormats();
5238
+ const pre = closestPre();
5239
+ setCodeLang(pre ? langOf(pre) : "plain");
4713
5240
  };
4714
5241
  const onToolbarAction = (item) => {
4715
- if (closestPre() && item !== "code-block") return;
5242
+ if (closestPre() && !["code-block", "undo", "redo"].includes(item)) return;
4716
5243
  switch (item) {
4717
5244
  case "bold":
4718
5245
  exec("bold");
@@ -4741,8 +5268,14 @@ var Editor = (props) => {
4741
5268
  handleInput();
4742
5269
  break;
4743
5270
  }
4744
- case "heading":
4745
- setBlock("h2");
5271
+ case "heading-1":
5272
+ toggleBlock("h1");
5273
+ break;
5274
+ case "heading-2":
5275
+ toggleBlock("h2");
5276
+ break;
5277
+ case "heading-3":
5278
+ toggleBlock("h3");
4746
5279
  break;
4747
5280
  case "list":
4748
5281
  exec("insertUnorderedList");
@@ -4765,6 +5298,39 @@ var Editor = (props) => {
4765
5298
  case "divider":
4766
5299
  insertDivider();
4767
5300
  break;
5301
+ case "checklist":
5302
+ toggleChecklist();
5303
+ break;
5304
+ case "align-left":
5305
+ setAlign("left");
5306
+ break;
5307
+ case "align-center":
5308
+ setAlign("center");
5309
+ break;
5310
+ case "align-right":
5311
+ setAlign("right");
5312
+ break;
5313
+ case "highlight":
5314
+ toggleHighlight();
5315
+ break;
5316
+ case "superscript":
5317
+ exec("superscript");
5318
+ break;
5319
+ case "subscript":
5320
+ exec("subscript");
5321
+ break;
5322
+ case "table":
5323
+ insertTable();
5324
+ break;
5325
+ case "clear-format":
5326
+ clearFormat();
5327
+ break;
5328
+ case "undo":
5329
+ undo();
5330
+ break;
5331
+ case "redo":
5332
+ redo();
5333
+ break;
4768
5334
  }
4769
5335
  };
4770
5336
  return /* @__PURE__ */ (0, import_jsx_runtime323.jsxs)("div", { className: clsx_default("lib-xplat-editor", isFocused && "focused", readOnly && "readonly"), children: [
@@ -4773,7 +5339,10 @@ var Editor = (props) => {
4773
5339
  {
4774
5340
  items: toolbar,
4775
5341
  active: activeFormats,
4776
- onAction: onToolbarAction
5342
+ onAction: onToolbarAction,
5343
+ codeLang,
5344
+ inCodeBlock: activeFormats.has("block:pre"),
5345
+ onCodeLangChange: changeCodeLang
4777
5346
  }
4778
5347
  ),
4779
5348
  /* @__PURE__ */ (0, import_jsx_runtime323.jsxs)("div", { className: "lib-xplat-editor__content", style: { minHeight }, children: [
@@ -4791,6 +5360,7 @@ var Editor = (props) => {
4791
5360
  className: "lib-xplat-editor__editable",
4792
5361
  onInput: handleInput,
4793
5362
  onKeyDown: handleKeyDown,
5363
+ onClick: handleEditorClick,
4794
5364
  onKeyUp: handleSelectionUpdate,
4795
5365
  onMouseUp: handleSelectionUpdate,
4796
5366
  onPaste: handlePaste,
@@ -4832,14 +5402,27 @@ var TOOLBAR_LABEL = {
4832
5402
  underline: "U",
4833
5403
  strikethrough: "S",
4834
5404
  code: "<>",
4835
- heading: "H",
5405
+ "heading-1": "H1",
5406
+ "heading-2": "H2",
5407
+ "heading-3": "H3",
4836
5408
  list: "\u2022",
4837
5409
  "ordered-list": "1.",
4838
5410
  blockquote: "\u275D",
4839
5411
  "code-block": "[ ]",
4840
5412
  link: "\u{1F517}",
4841
5413
  image: "\u{1F5BC}",
4842
- divider: "\u2014"
5414
+ divider: "\u2014",
5415
+ checklist: "\u2611",
5416
+ "align-left": "\u21E4",
5417
+ "align-center": "\u2194",
5418
+ "align-right": "\u21E5",
5419
+ highlight: "\u{1F58D}",
5420
+ superscript: "x\xB2",
5421
+ subscript: "x\u2082",
5422
+ table: "\u25A6",
5423
+ "clear-format": "\u232B",
5424
+ undo: "\u21B6",
5425
+ redo: "\u21B7"
4843
5426
  };
4844
5427
  var TOOLBAR_TITLE = {
4845
5428
  bold: "\uAD75\uAC8C (\u2318B)",
@@ -4847,36 +5430,74 @@ var TOOLBAR_TITLE = {
4847
5430
  underline: "\uBC11\uC904 (\u2318U)",
4848
5431
  strikethrough: "\uCDE8\uC18C\uC120",
4849
5432
  code: "\uC778\uB77C\uC778 \uCF54\uB4DC",
4850
- heading: "\uC81C\uBAA9",
5433
+ "heading-1": "\uC81C\uBAA9 1",
5434
+ "heading-2": "\uC81C\uBAA9 2",
5435
+ "heading-3": "\uC81C\uBAA9 3",
4851
5436
  list: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D",
4852
5437
  "ordered-list": "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D",
4853
5438
  blockquote: "\uC778\uC6A9",
4854
5439
  "code-block": "\uCF54\uB4DC \uBE14\uB85D",
4855
5440
  link: "\uB9C1\uD06C (\u2318K)",
4856
5441
  image: "\uC774\uBBF8\uC9C0",
4857
- divider: "\uAD6C\uBD84\uC120"
5442
+ divider: "\uAD6C\uBD84\uC120",
5443
+ checklist: "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
5444
+ "align-left": "\uC67C\uCABD \uC815\uB82C",
5445
+ "align-center": "\uAC00\uC6B4\uB370 \uC815\uB82C",
5446
+ "align-right": "\uC624\uB978\uCABD \uC815\uB82C",
5447
+ highlight: "\uD615\uAD11\uD39C",
5448
+ superscript: "\uC704 \uCCA8\uC790",
5449
+ subscript: "\uC544\uB798 \uCCA8\uC790",
5450
+ table: "\uD45C \uC0BD\uC785",
5451
+ "clear-format": "\uC11C\uC2DD \uC9C0\uC6B0\uAE30",
5452
+ undo: "\uB418\uB3CC\uB9AC\uAE30 (\u2318Z)",
5453
+ redo: "\uB2E4\uC2DC\uD558\uAE30 (\u2318\u21E7Z)"
4858
5454
  };
4859
5455
  var isActive = (item, active) => {
4860
- if (item === "heading") return active.has("block:h1") || active.has("block:h2") || active.has("block:h3");
5456
+ if (item === "heading-1") return active.has("block:h1");
5457
+ if (item === "heading-2") return active.has("block:h2");
5458
+ if (item === "heading-3") return active.has("block:h3");
4861
5459
  if (item === "blockquote") return active.has("block:blockquote");
4862
5460
  if (item === "code-block") return active.has("block:pre");
5461
+ if (item === "align-left") return active.has("align:left");
5462
+ if (item === "align-center") return active.has("align:center");
5463
+ if (item === "align-right") return active.has("align:right");
4863
5464
  return active.has(item);
4864
5465
  };
4865
- var EditorToolbar = ({ items, active, onAction }) => {
4866
- return /* @__PURE__ */ (0, import_jsx_runtime323.jsx)("div", { className: "lib-xplat-editor__toolbar", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime323.jsx)(
4867
- "button",
4868
- {
4869
- type: "button",
4870
- className: clsx_default("lib-xplat-editor__toolbar-btn", isActive(item, active) && "active"),
4871
- title: TOOLBAR_TITLE[item],
4872
- onMouseDown: (e) => {
4873
- e.preventDefault();
4874
- onAction(item);
5466
+ var EditorToolbar = ({
5467
+ items,
5468
+ active,
5469
+ onAction,
5470
+ codeLang,
5471
+ inCodeBlock,
5472
+ onCodeLangChange
5473
+ }) => {
5474
+ return /* @__PURE__ */ (0, import_jsx_runtime323.jsxs)("div", { className: "lib-xplat-editor__toolbar", children: [
5475
+ items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime323.jsx)(
5476
+ "button",
5477
+ {
5478
+ type: "button",
5479
+ className: clsx_default("lib-xplat-editor__toolbar-btn", isActive(item, active) && "active"),
5480
+ title: TOOLBAR_TITLE[item],
5481
+ onMouseDown: (e) => {
5482
+ e.preventDefault();
5483
+ onAction(item);
5484
+ },
5485
+ children: TOOLBAR_LABEL[item]
4875
5486
  },
4876
- children: TOOLBAR_LABEL[item]
4877
- },
4878
- item
4879
- )) });
5487
+ item
5488
+ )),
5489
+ inCodeBlock && /* @__PURE__ */ (0, import_jsx_runtime323.jsx)(
5490
+ "select",
5491
+ {
5492
+ className: "lib-xplat-editor__lang",
5493
+ value: codeLang,
5494
+ "aria-label": "\uCF54\uB4DC \uC5B8\uC5B4",
5495
+ onMouseDown: (e) => e.stopPropagation(),
5496
+ onChange: (e) => onCodeLangChange(e.target.value),
5497
+ children: CODE_LANGUAGES.map((l) => /* @__PURE__ */ (0, import_jsx_runtime323.jsx)("option", { value: l.value, children: l.label }, l.value))
5498
+ }
5499
+ )
5500
+ ] });
4880
5501
  };
4881
5502
  var SlashMenu = ({ position, items, activeIndex, onSelect, onClose }) => {
4882
5503
  import_react22.default.useEffect(() => {
@@ -4980,10 +5601,13 @@ var FileUpload = (props) => {
4980
5601
  multiple = false,
4981
5602
  maxSize,
4982
5603
  onChange,
4983
- label = "\uD30C\uC77C\uC744 \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \uD074\uB9AD\uD558\uC5EC \uC5C5\uB85C\uB4DC",
5604
+ placeholder = "\uD30C\uC77C\uC744 \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \uD074\uB9AD\uD558\uC5EC \uC5C5\uB85C\uB4DC",
4984
5605
  description,
4985
- validations
5606
+ validations,
5607
+ label,
5608
+ required
4986
5609
  } = props;
5610
+ const fieldId = import_react23.default.useId();
4987
5611
  const [isDragOver, setIsDragOver] = import_react23.default.useState(false);
4988
5612
  const inputRef = import_react23.default.useRef(null);
4989
5613
  const handleFiles = (fileList) => {
@@ -5028,6 +5652,7 @@ var FileUpload = (props) => {
5028
5652
  "input",
5029
5653
  {
5030
5654
  ref: inputRef,
5655
+ id: fieldId,
5031
5656
  type: "file",
5032
5657
  accept,
5033
5658
  multiple,
@@ -5036,12 +5661,12 @@ var FileUpload = (props) => {
5036
5661
  }
5037
5662
  ),
5038
5663
  /* @__PURE__ */ (0, import_jsx_runtime325.jsx)("div", { className: "upload-icon", children: /* @__PURE__ */ (0, import_jsx_runtime325.jsx)(UploadIcon_default, {}) }),
5039
- /* @__PURE__ */ (0, import_jsx_runtime325.jsx)("p", { className: "upload-label", children: label }),
5664
+ /* @__PURE__ */ (0, import_jsx_runtime325.jsx)("p", { className: "upload-label", children: placeholder }),
5040
5665
  description && /* @__PURE__ */ (0, import_jsx_runtime325.jsx)("p", { className: "upload-description", children: description })
5041
5666
  ]
5042
5667
  }
5043
5668
  );
5044
- return /* @__PURE__ */ (0, import_jsx_runtime325.jsx)(Field, { validations, children: control });
5669
+ return /* @__PURE__ */ (0, import_jsx_runtime325.jsx)(Field, { label, required, htmlFor: fieldId, validations, children: control });
5045
5670
  };
5046
5671
  FileUpload.displayName = "FileUpload";
5047
5672
  var FileUpload_default = FileUpload;
@@ -5184,7 +5809,8 @@ var HtmlTypeWriter_default = HtmlTypeWriter;
5184
5809
  var import_react26 = __toESM(require("react"), 1);
5185
5810
  var import_jsx_runtime327 = require("react/jsx-runtime");
5186
5811
  var ImageSelector = (props) => {
5187
- const { value, label, onChange, validations } = props;
5812
+ const { value, placeholder, onChange, validations, label, required } = props;
5813
+ const fieldId = import_react26.default.useId();
5188
5814
  const [previewUrl, setPreviewUrl] = import_react26.default.useState();
5189
5815
  import_react26.default.useEffect(() => {
5190
5816
  if (!value) {
@@ -5214,7 +5840,7 @@ var ImageSelector = (props) => {
5214
5840
  {
5215
5841
  type: "file",
5216
5842
  accept: "image/*",
5217
- id: "image-input",
5843
+ id: fieldId,
5218
5844
  onChange: handleFileChange,
5219
5845
  ref: inputRef,
5220
5846
  "aria-invalid": hasError(validations) || void 0
@@ -5226,10 +5852,10 @@ var ImageSelector = (props) => {
5226
5852
  ] }),
5227
5853
  /* @__PURE__ */ (0, import_jsx_runtime327.jsx)("div", { className: "content", children: previewUrl ? /* @__PURE__ */ (0, import_jsx_runtime327.jsx)("img", { src: previewUrl, alt: "preview" }) : /* @__PURE__ */ (0, import_jsx_runtime327.jsxs)("div", { className: "skeleton", onClick: handleOpenFileDialog, children: [
5228
5854
  /* @__PURE__ */ (0, import_jsx_runtime327.jsx)("div", { className: "icon-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime327.jsx)(ImageIcon_default, {}) }),
5229
- /* @__PURE__ */ (0, import_jsx_runtime327.jsx)("div", { className: "label", children: label || "\uC774\uBBF8\uC9C0 \uCD94\uAC00\uD558\uAE30" })
5855
+ /* @__PURE__ */ (0, import_jsx_runtime327.jsx)("div", { className: "label", children: placeholder || "\uC774\uBBF8\uC9C0 \uCD94\uAC00\uD558\uAE30" })
5230
5856
  ] }) })
5231
5857
  ] });
5232
- return /* @__PURE__ */ (0, import_jsx_runtime327.jsx)(Field, { validations, children: control });
5858
+ return /* @__PURE__ */ (0, import_jsx_runtime327.jsx)(Field, { label, required, htmlFor: fieldId, validations, children: control });
5233
5859
  };
5234
5860
  ImageSelector.displayName = "ImageSelector";
5235
5861
  var ImageSelector_default = ImageSelector;
@@ -5421,6 +6047,7 @@ var PopOver = (props) => {
5421
6047
  className: clsx_default(
5422
6048
  "lib-xplat-pop-over-content",
5423
6049
  position.direction,
6050
+ position.repositioning && "repositioning",
5424
6051
  eventTrigger && "visible"
5425
6052
  ),
5426
6053
  ref: popRef,
@@ -5767,7 +6394,7 @@ var SelectRoot = (props) => {
5767
6394
  mounted && /* @__PURE__ */ (0, import_jsx_runtime337.jsx)(Portal_default, { children: /* @__PURE__ */ (0, import_jsx_runtime337.jsxs)(
5768
6395
  "div",
5769
6396
  {
5770
- className: clsx_default("lib-xplat-select-content", size, position.direction, stateClass),
6397
+ className: clsx_default("lib-xplat-select-content", size, position.direction, stateClass, position.repositioning && "repositioning"),
5771
6398
  ref: contentRef,
5772
6399
  style: { ...position.position, width: triggerRef.current?.offsetWidth },
5773
6400
  role: "listbox",
@@ -6660,7 +7287,7 @@ var TimePicker = (props) => {
6660
7287
  "div",
6661
7288
  {
6662
7289
  ref: contentRef,
6663
- className: clsx_default("lib-xplat-time-picker-content", size, visible ? "enter" : "exit"),
7290
+ className: clsx_default("lib-xplat-time-picker-content", size, visible ? "enter" : "exit", position.repositioning && "repositioning"),
6664
7291
  style: { ...position.position, width: triggerRef.current?.offsetWidth },
6665
7292
  role: "listbox",
6666
7293
  children: options.map((t) => /* @__PURE__ */ (0, import_jsx_runtime351.jsx)(