@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
@@ -1532,11 +1532,11 @@ var FieldMessage = (props) => {
1532
1532
  FieldMessage.displayName = "FieldMessage";
1533
1533
  var FieldMessage_default = FieldMessage;
1534
1534
  var Field = (props) => {
1535
- const { label, required = false, htmlFor, validations, children } = props;
1535
+ const { label, required = false, htmlFor, labelId, validations, children } = props;
1536
1536
  const hasMessages = !!validations && validations.length > 0;
1537
1537
  if (!label && !hasMessages) return /* @__PURE__ */ jsx297(Fragment, { children });
1538
1538
  return /* @__PURE__ */ jsxs191("div", { className: "lib-xplat-field", children: [
1539
- label && /* @__PURE__ */ jsxs191("label", { className: "lib-xplat-field-label", htmlFor, children: [
1539
+ label && /* @__PURE__ */ jsxs191("label", { className: "lib-xplat-field-label", id: labelId, htmlFor, children: [
1540
1540
  label,
1541
1541
  required && /* @__PURE__ */ jsx297("span", { className: "required", "aria-hidden": "true", children: "*" })
1542
1542
  ] }),
@@ -3303,8 +3303,12 @@ var SingleDatePicker = (props) => {
3303
3303
  highlightDates = [],
3304
3304
  locale = "ko",
3305
3305
  rangeStart,
3306
- rangeEnd
3306
+ rangeEnd,
3307
+ label,
3308
+ required,
3309
+ validations
3307
3310
  } = props;
3311
+ const labelId = React12.useId();
3308
3312
  const initialYear = value?.getFullYear();
3309
3313
  const initialMonth = value?.getMonth();
3310
3314
  const { year, month, days, goToPrevMonth, goToNextMonth, setYear, setMonth } = useCalendar(
@@ -3367,10 +3371,12 @@ var SingleDatePicker = (props) => {
3367
3371
  const monthLabels = MONTH_LABELS[locale];
3368
3372
  const titleText = pickerMode === "days" ? locale === "ko" ? `${year}\uB144 ${monthLabels[month]}` : `${monthLabels[month]} ${year}` : pickerMode === "months" ? `${year}` : `${yearRangeStart} - ${yearRangeStart + 11}`;
3369
3373
  const hasRange = rangeStart != null && rangeEnd != null;
3370
- return /* @__PURE__ */ jsxs200(
3374
+ const calendar = /* @__PURE__ */ jsxs200(
3371
3375
  "div",
3372
3376
  {
3373
3377
  className: clsx_default("lib-xplat-datepicker", "single"),
3378
+ role: "group",
3379
+ "aria-labelledby": label ? labelId : void 0,
3374
3380
  children: [
3375
3381
  /* @__PURE__ */ jsxs200("div", { className: "datepicker-header", children: [
3376
3382
  /* @__PURE__ */ jsx314("button", { className: "datepicker-nav", onClick: handlePrev, type: "button", children: /* @__PURE__ */ jsx314(ChevronLeftIcon_default, {}) }),
@@ -3391,18 +3397,18 @@ var SingleDatePicker = (props) => {
3391
3397
  y
3392
3398
  );
3393
3399
  }) }),
3394
- pickerMode === "months" && /* @__PURE__ */ jsx314("div", { className: "datepicker-picker-grid", children: monthLabels.map((label, i) => /* @__PURE__ */ jsx314(
3400
+ pickerMode === "months" && /* @__PURE__ */ jsx314("div", { className: "datepicker-picker-grid", children: monthLabels.map((label2, i) => /* @__PURE__ */ jsx314(
3395
3401
  "button",
3396
3402
  {
3397
3403
  type: "button",
3398
3404
  className: clsx_default("datepicker-picker-cell", i === month && "active"),
3399
3405
  onClick: () => handleMonthSelect(i),
3400
- children: label
3406
+ children: label2
3401
3407
  },
3402
3408
  i
3403
3409
  )) }),
3404
3410
  pickerMode === "days" && /* @__PURE__ */ jsxs200(Fragment4, { children: [
3405
- /* @__PURE__ */ jsx314("div", { className: "datepicker-weekdays", children: weekdays.map((label, i) => /* @__PURE__ */ jsx314(
3411
+ /* @__PURE__ */ jsx314("div", { className: "datepicker-weekdays", children: weekdays.map((label2, i) => /* @__PURE__ */ jsx314(
3406
3412
  "div",
3407
3413
  {
3408
3414
  className: clsx_default(
@@ -3410,9 +3416,9 @@ var SingleDatePicker = (props) => {
3410
3416
  i === 0 && "sunday",
3411
3417
  i === 6 && "saturday"
3412
3418
  ),
3413
- children: label
3419
+ children: label2
3414
3420
  },
3415
- label
3421
+ label2
3416
3422
  )) }),
3417
3423
  /* @__PURE__ */ jsx314("div", { className: "datepicker-grid", children: days.map((day, idx) => {
3418
3424
  const t = day.date.getTime();
@@ -3444,6 +3450,7 @@ var SingleDatePicker = (props) => {
3444
3450
  ]
3445
3451
  }
3446
3452
  );
3453
+ return /* @__PURE__ */ jsx314(Field, { label, required, labelId, validations, children: calendar });
3447
3454
  };
3448
3455
  SingleDatePicker.displayName = "SingleDatePicker";
3449
3456
  var SingleDatePicker_default = SingleDatePicker;
@@ -3458,7 +3465,18 @@ var formatDate = (date) => {
3458
3465
  return `${y}/${m}/${d}`;
3459
3466
  };
3460
3467
  var InputDatePicker = (props) => {
3461
- const { value, onChange, minDate, maxDate, disabled, locale = "ko", placeholder } = props;
3468
+ const {
3469
+ value,
3470
+ onChange,
3471
+ minDate,
3472
+ maxDate,
3473
+ disabled,
3474
+ locale = "ko",
3475
+ placeholder,
3476
+ label,
3477
+ required,
3478
+ validations
3479
+ } = props;
3462
3480
  const [isOpen, setIsOpen] = React13.useState(false);
3463
3481
  const [tempDate, setTempDate] = React13.useState(value ?? /* @__PURE__ */ new Date());
3464
3482
  const handleOpen = () => {
@@ -3484,6 +3502,9 @@ var InputDatePicker = (props) => {
3484
3502
  placeholder,
3485
3503
  suffix: /* @__PURE__ */ jsx315(CalenderIcon_default, {}),
3486
3504
  disabled,
3505
+ label,
3506
+ required,
3507
+ validations,
3487
3508
  readOnly: true
3488
3509
  }
3489
3510
  ) }),
@@ -3615,8 +3636,12 @@ var RangePicker = (props) => {
3615
3636
  onChange,
3616
3637
  minDate,
3617
3638
  maxDate,
3618
- locale = "ko"
3639
+ locale = "ko",
3640
+ label,
3641
+ required,
3642
+ validations
3619
3643
  } = props;
3644
+ const labelId = React16.useId();
3620
3645
  const [activeTab, setActiveTab] = React16.useState("start");
3621
3646
  const handleStartChange = (date) => {
3622
3647
  if (!date) return;
@@ -3630,70 +3655,79 @@ var RangePicker = (props) => {
3630
3655
  };
3631
3656
  const startMaxDate = maxDate && endDate < maxDate ? endDate : endDate;
3632
3657
  const endMinDate = minDate && startDate > minDate ? startDate : startDate;
3633
- return /* @__PURE__ */ jsxs203("div", { className: clsx_default("lib-xplat-datepicker", "range"), children: [
3634
- /* @__PURE__ */ jsx318("div", { className: "datepicker-range-tabs", children: /* @__PURE__ */ jsx318(
3635
- Tab_default,
3636
- {
3637
- activeIndex: activeTab === "start" ? 0 : 1,
3638
- tabs: [
3639
- { value: "start", title: locale === "ko" ? "\uC2DC\uC791\uC77C" : "Start" },
3640
- { value: "end", title: locale === "ko" ? "\uC885\uB8CC\uC77C" : "End" }
3641
- ],
3642
- onChange: (tab) => setActiveTab(tab.value),
3643
- type: "toggle",
3644
- size: "sm"
3645
- }
3646
- ) }),
3647
- /* @__PURE__ */ jsxs203("div", { className: "datepicker-range-panels", children: [
3648
- /* @__PURE__ */ jsx318(
3649
- SingleDatePicker_default,
3650
- {
3651
- value: startDate,
3652
- onChange: handleStartChange,
3653
- minDate,
3654
- maxDate: startMaxDate,
3655
- rangeStart: startDate,
3656
- rangeEnd: endDate,
3657
- locale
3658
- }
3659
- ),
3660
- /* @__PURE__ */ jsx318(
3661
- SingleDatePicker_default,
3662
- {
3663
- value: endDate,
3664
- onChange: handleEndChange,
3665
- minDate: endMinDate,
3666
- maxDate,
3667
- rangeStart: startDate,
3668
- rangeEnd: endDate,
3669
- locale
3670
- }
3671
- )
3672
- ] }),
3673
- /* @__PURE__ */ jsx318("div", { className: "datepicker-range-mobile", children: activeTab === "start" ? /* @__PURE__ */ jsx318(
3674
- SingleDatePicker_default,
3675
- {
3676
- value: startDate,
3677
- onChange: handleStartChange,
3678
- minDate,
3679
- maxDate: startMaxDate,
3680
- rangeStart: startDate,
3681
- rangeEnd: endDate,
3682
- locale
3683
- }
3684
- ) : /* @__PURE__ */ jsx318(
3685
- SingleDatePicker_default,
3686
- {
3687
- value: endDate,
3688
- onChange: handleEndChange,
3689
- minDate: endMinDate,
3690
- maxDate,
3691
- rangeStart: startDate,
3692
- rangeEnd: endDate,
3693
- locale
3694
- }
3695
- ) })
3696
- ] });
3658
+ const calendar = /* @__PURE__ */ jsxs203(
3659
+ "div",
3660
+ {
3661
+ className: clsx_default("lib-xplat-datepicker", "range"),
3662
+ role: "group",
3663
+ "aria-labelledby": label ? labelId : void 0,
3664
+ children: [
3665
+ /* @__PURE__ */ jsx318("div", { className: "datepicker-range-tabs", children: /* @__PURE__ */ jsx318(
3666
+ Tab_default,
3667
+ {
3668
+ activeIndex: activeTab === "start" ? 0 : 1,
3669
+ tabs: [
3670
+ { value: "start", title: locale === "ko" ? "\uC2DC\uC791\uC77C" : "Start" },
3671
+ { value: "end", title: locale === "ko" ? "\uC885\uB8CC\uC77C" : "End" }
3672
+ ],
3673
+ onChange: (tab) => setActiveTab(tab.value),
3674
+ type: "toggle",
3675
+ size: "sm"
3676
+ }
3677
+ ) }),
3678
+ /* @__PURE__ */ jsxs203("div", { className: "datepicker-range-panels", children: [
3679
+ /* @__PURE__ */ jsx318(
3680
+ SingleDatePicker_default,
3681
+ {
3682
+ value: startDate,
3683
+ onChange: handleStartChange,
3684
+ minDate,
3685
+ maxDate: startMaxDate,
3686
+ rangeStart: startDate,
3687
+ rangeEnd: endDate,
3688
+ locale
3689
+ }
3690
+ ),
3691
+ /* @__PURE__ */ jsx318(
3692
+ SingleDatePicker_default,
3693
+ {
3694
+ value: endDate,
3695
+ onChange: handleEndChange,
3696
+ minDate: endMinDate,
3697
+ maxDate,
3698
+ rangeStart: startDate,
3699
+ rangeEnd: endDate,
3700
+ locale
3701
+ }
3702
+ )
3703
+ ] }),
3704
+ /* @__PURE__ */ jsx318("div", { className: "datepicker-range-mobile", children: activeTab === "start" ? /* @__PURE__ */ jsx318(
3705
+ SingleDatePicker_default,
3706
+ {
3707
+ value: startDate,
3708
+ onChange: handleStartChange,
3709
+ minDate,
3710
+ maxDate: startMaxDate,
3711
+ rangeStart: startDate,
3712
+ rangeEnd: endDate,
3713
+ locale
3714
+ }
3715
+ ) : /* @__PURE__ */ jsx318(
3716
+ SingleDatePicker_default,
3717
+ {
3718
+ value: endDate,
3719
+ onChange: handleEndChange,
3720
+ minDate: endMinDate,
3721
+ maxDate,
3722
+ rangeStart: startDate,
3723
+ rangeEnd: endDate,
3724
+ locale
3725
+ }
3726
+ ) })
3727
+ ]
3728
+ }
3729
+ );
3730
+ return /* @__PURE__ */ jsx318(Field, { label, required, labelId, validations, children: calendar });
3697
3731
  };
3698
3732
  RangePicker.displayName = "RangePicker";
3699
3733
  var RangePicker_default = RangePicker;
@@ -3842,8 +3876,10 @@ import React19 from "react";
3842
3876
  var useAutoPosition = (triggerRef, popRef, enabled = true) => {
3843
3877
  const [position, setPosition] = React19.useState({
3844
3878
  position: {},
3845
- direction: "bottom"
3879
+ direction: "bottom",
3880
+ repositioning: false
3846
3881
  });
3882
+ const placedRef = React19.useRef(false);
3847
3883
  const calculatePosition = React19.useCallback(() => {
3848
3884
  if (!triggerRef.current || !popRef.current) return;
3849
3885
  const triggerRect = triggerRef.current.getBoundingClientRect();
@@ -3868,11 +3904,16 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
3868
3904
  if (left < 8) left = 8;
3869
3905
  setPosition({
3870
3906
  position: { top, left },
3871
- direction
3907
+ direction,
3908
+ repositioning: placedRef.current
3872
3909
  });
3910
+ placedRef.current = true;
3873
3911
  }, [triggerRef, popRef]);
3874
3912
  React19.useLayoutEffect(() => {
3875
- if (!enabled) return;
3913
+ if (!enabled) {
3914
+ placedRef.current = false;
3915
+ return;
3916
+ }
3876
3917
  calculatePosition();
3877
3918
  const raf = requestAnimationFrame(calculatePosition);
3878
3919
  return () => cancelAnimationFrame(raf);
@@ -3887,11 +3928,17 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
3887
3928
  }, [calculatePosition, enabled, popRef]);
3888
3929
  React19.useEffect(() => {
3889
3930
  if (!enabled) return;
3890
- window.addEventListener("resize", calculatePosition);
3891
- window.addEventListener("scroll", calculatePosition, true);
3931
+ let raf = 0;
3932
+ const onScrollOrResize = () => {
3933
+ cancelAnimationFrame(raf);
3934
+ raf = requestAnimationFrame(calculatePosition);
3935
+ };
3936
+ window.addEventListener("resize", onScrollOrResize, { passive: true });
3937
+ window.addEventListener("scroll", onScrollOrResize, { passive: true, capture: true });
3892
3938
  return () => {
3893
- window.removeEventListener("resize", calculatePosition);
3894
- window.removeEventListener("scroll", calculatePosition, true);
3939
+ cancelAnimationFrame(raf);
3940
+ window.removeEventListener("resize", onScrollOrResize);
3941
+ window.removeEventListener("scroll", onScrollOrResize, true);
3895
3942
  };
3896
3943
  }, [calculatePosition, enabled]);
3897
3944
  return position;
@@ -3932,7 +3979,7 @@ var Dropdown = (props) => {
3932
3979
  const [visible, setVisible] = React21.useState(false);
3933
3980
  const triggerRef = React21.useRef(null);
3934
3981
  const menuRef = React21.useRef(null);
3935
- const { position, direction } = useAutoPosition_default(triggerRef, menuRef, mounted);
3982
+ const { position, direction, repositioning } = useAutoPosition_default(triggerRef, menuRef, mounted);
3936
3983
  useClickOutside_default([triggerRef, menuRef], () => setIsOpen(false), isOpen);
3937
3984
  useEscapeKey(isOpen, () => {
3938
3985
  setIsOpen(false);
@@ -3967,7 +4014,7 @@ var Dropdown = (props) => {
3967
4014
  "div",
3968
4015
  {
3969
4016
  ref: menuRef,
3970
- className: clsx_default("lib-xplat-dropdown-menu", direction, { visible }),
4017
+ className: clsx_default("lib-xplat-dropdown-menu", direction, { visible }, repositioning && "repositioning"),
3971
4018
  style: { top: position.top, left: position.left },
3972
4019
  role: "menu",
3973
4020
  children: items.map((item) => /* @__PURE__ */ jsx322(
@@ -3993,6 +4040,111 @@ var Dropdown_default = Dropdown;
3993
4040
 
3994
4041
  // src/components/Editor/Editor.tsx
3995
4042
  import React22 from "react";
4043
+
4044
+ // src/components/Editor/highlight.ts
4045
+ var CODE_LANGUAGES = [
4046
+ { value: "plain", label: "\uC5C6\uC74C" },
4047
+ { value: "javascript", label: "JavaScript" },
4048
+ { value: "typescript", label: "TypeScript" },
4049
+ { value: "json", label: "JSON" },
4050
+ { value: "html", label: "HTML" },
4051
+ { value: "css", label: "CSS" },
4052
+ { value: "python", label: "Python" },
4053
+ { value: "sql", label: "SQL" },
4054
+ { value: "bash", label: "Bash" }
4055
+ ];
4056
+ var KEYWORDS = {
4057
+ plain: [],
4058
+ 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(" "),
4059
+ 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(" "),
4060
+ json: ["true", "false", "null"],
4061
+ html: [],
4062
+ css: [],
4063
+ 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(" "),
4064
+ 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(" "),
4065
+ 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(" ")
4066
+ };
4067
+ var LINE_COMMENT = {
4068
+ javascript: "//",
4069
+ typescript: "//",
4070
+ css: "",
4071
+ python: "#",
4072
+ sql: "--",
4073
+ bash: "#"
4074
+ };
4075
+ var escape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4076
+ var wrap = (cls, text) => `<span class="tok-${cls}">${escape(text)}</span>`;
4077
+ var highlight = (code, lang) => {
4078
+ if (lang === "plain" || !code) return escape(code);
4079
+ const keywords = new Set(KEYWORDS[lang]);
4080
+ const lineComment = LINE_COMMENT[lang];
4081
+ const caseInsensitive = lang === "sql";
4082
+ let out = "";
4083
+ let i = 0;
4084
+ while (i < code.length) {
4085
+ const rest = code.slice(i);
4086
+ if ((lang === "javascript" || lang === "typescript" || lang === "css") && rest.startsWith("/*")) {
4087
+ const end = code.indexOf("*/", i + 2);
4088
+ const stop = end === -1 ? code.length : end + 2;
4089
+ out += wrap("comment", code.slice(i, stop));
4090
+ i = stop;
4091
+ continue;
4092
+ }
4093
+ if (lineComment && rest.startsWith(lineComment)) {
4094
+ const nl = code.indexOf("\n", i);
4095
+ const stop = nl === -1 ? code.length : nl;
4096
+ out += wrap("comment", code.slice(i, stop));
4097
+ i = stop;
4098
+ continue;
4099
+ }
4100
+ const q = rest[0];
4101
+ if (q === '"' || q === "'" || q === "`") {
4102
+ let j = 1;
4103
+ while (j < rest.length) {
4104
+ if (rest[j] === "\\") {
4105
+ j += 2;
4106
+ continue;
4107
+ }
4108
+ if (rest[j] === q) {
4109
+ j += 1;
4110
+ break;
4111
+ }
4112
+ if (rest[j] === "\n" && q !== "`") break;
4113
+ j += 1;
4114
+ }
4115
+ out += wrap("string", rest.slice(0, j));
4116
+ i += j;
4117
+ continue;
4118
+ }
4119
+ const num = /^\d+(\.\d+)?/.exec(rest);
4120
+ if (num && !/[\w$]/.test(code[i - 1] ?? "")) {
4121
+ out += wrap("number", num[0]);
4122
+ i += num[0].length;
4123
+ continue;
4124
+ }
4125
+ const word = /^[A-Za-z_$][\w$]*/.exec(rest);
4126
+ if (word) {
4127
+ const w = word[0];
4128
+ const hit = caseInsensitive ? keywords.has(w.toUpperCase()) : keywords.has(w);
4129
+ out += hit ? wrap("keyword", w) : escape(w);
4130
+ i += w.length;
4131
+ continue;
4132
+ }
4133
+ if (lang === "html" && rest.startsWith("<")) {
4134
+ const tag = /^<\/?[A-Za-z][\w-]*/.exec(rest);
4135
+ if (tag) {
4136
+ out += wrap("keyword", tag[0]);
4137
+ i += tag[0].length;
4138
+ continue;
4139
+ }
4140
+ }
4141
+ out += escape(code[i]);
4142
+ i += 1;
4143
+ }
4144
+ return out;
4145
+ };
4146
+
4147
+ // src/components/Editor/Editor.tsx
3996
4148
  import { jsx as jsx323, jsxs as jsxs207 } from "react/jsx-runtime";
3997
4149
  var DEFAULT_TOOLBAR = [
3998
4150
  "bold",
@@ -4000,14 +4152,27 @@ var DEFAULT_TOOLBAR = [
4000
4152
  "underline",
4001
4153
  "strikethrough",
4002
4154
  "code",
4003
- "heading",
4155
+ "heading-1",
4156
+ "heading-2",
4157
+ "heading-3",
4004
4158
  "list",
4005
4159
  "ordered-list",
4006
4160
  "blockquote",
4007
4161
  "code-block",
4008
4162
  "link",
4009
4163
  "image",
4010
- "divider"
4164
+ "divider",
4165
+ "checklist",
4166
+ "table",
4167
+ "align-left",
4168
+ "align-center",
4169
+ "align-right",
4170
+ "highlight",
4171
+ "superscript",
4172
+ "subscript",
4173
+ "clear-format",
4174
+ "undo",
4175
+ "redo"
4011
4176
  ];
4012
4177
  var ALLOWED_TAGS = /* @__PURE__ */ new Set([
4013
4178
  "p",
@@ -4036,11 +4201,41 @@ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
4036
4201
  "img",
4037
4202
  "hr",
4038
4203
  "span",
4039
- "div"
4204
+ "div",
4205
+ "sub",
4206
+ "sup",
4207
+ "mark",
4208
+ "table",
4209
+ "thead",
4210
+ "tbody",
4211
+ "tr",
4212
+ "th",
4213
+ "td"
4040
4214
  ]);
4041
4215
  var ALLOWED_ATTRS = {
4042
4216
  a: ["href", "target", "rel"],
4043
- img: ["src", "alt"]
4217
+ img: ["src", "alt", "width"],
4218
+ // 정렬·체크리스트는 클래스로만 표현한다. style 을 열면 붙여넣기로 아무 CSS 나 들어온다
4219
+ p: ["class"],
4220
+ h1: ["class"],
4221
+ h2: ["class"],
4222
+ h3: ["class"],
4223
+ h4: ["class"],
4224
+ h5: ["class"],
4225
+ h6: ["class"],
4226
+ ul: ["class"],
4227
+ ol: ["class"],
4228
+ li: ["class", "data-checked"],
4229
+ blockquote: ["class"],
4230
+ div: ["class"],
4231
+ span: ["class"],
4232
+ pre: ["class"],
4233
+ code: ["class"],
4234
+ mark: ["class"],
4235
+ table: ["class"],
4236
+ td: ["class"],
4237
+ th: ["class"],
4238
+ tr: ["class"]
4044
4239
  };
4045
4240
  var DROP_WITH_CONTENT = /* @__PURE__ */ new Set([
4046
4241
  "script",
@@ -4093,7 +4288,9 @@ var escapeHtml = (text) => {
4093
4288
  };
4094
4289
  var SLASH_ITEMS = [
4095
4290
  { key: "paragraph", label: "\uB2E8\uB77D", hint: "\uAE30\uBCF8 \uD14D\uC2A4\uD2B8" },
4096
- { key: "heading", label: "\uC81C\uBAA9 1", hint: "\uD070 \uC81C\uBAA9" },
4291
+ { key: "heading-1", label: "\uC81C\uBAA9 1", hint: "\uAC00\uC7A5 \uD070 \uC81C\uBAA9" },
4292
+ { key: "heading-2", label: "\uC81C\uBAA9 2", hint: "" },
4293
+ { key: "heading-3", label: "\uC81C\uBAA9 3", hint: "" },
4097
4294
  { key: "list", label: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D", hint: "\u2022 \uD56D\uBAA9" },
4098
4295
  { key: "ordered-list", label: "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D", hint: "1. \uD56D\uBAA9" },
4099
4296
  { key: "blockquote", label: "\uC778\uC6A9", hint: "" },
@@ -4136,6 +4333,8 @@ var Editor = (props) => {
4136
4333
  el.innerHTML = sanitizeHtml(value || "");
4137
4334
  if (!el.firstChild) el.innerHTML = "<p><br></p>";
4138
4335
  lastEmittedRef.current = null;
4336
+ historyRef.current = [{ html: el.innerHTML, caret: 0 }];
4337
+ historyIdxRef.current = 0;
4139
4338
  updateEmpty();
4140
4339
  }, [value]);
4141
4340
  const updateEmpty = () => {
@@ -4180,6 +4379,17 @@ var Editor = (props) => {
4180
4379
  if (document.queryCommandState("insertOrderedList")) next.add("ordered-list");
4181
4380
  const block = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
4182
4381
  if (block) next.add(`block:${block}`);
4382
+ if (document.queryCommandState("superscript")) next.add("superscript");
4383
+ if (document.queryCommandState("subscript")) next.add("subscript");
4384
+ } catch {
4385
+ }
4386
+ try {
4387
+ const blockEl = currentBlock();
4388
+ if (blockEl?.classList.contains("align-center")) next.add("align:center");
4389
+ else if (blockEl?.classList.contains("align-right")) next.add("align:right");
4390
+ else if (blockEl) next.add("align:left");
4391
+ if (blockEl?.closest("mark")) next.add("highlight");
4392
+ if (blockEl?.closest("ul.checklist")) next.add("checklist");
4183
4393
  } catch {
4184
4394
  }
4185
4395
  setActiveFormats(next);
@@ -4193,6 +4403,122 @@ var Editor = (props) => {
4193
4403
  const setBlock = (tag) => {
4194
4404
  exec("formatBlock", tag);
4195
4405
  };
4406
+ const toggleBlock = (tag) => {
4407
+ const current = String(document.queryCommandValue("formatBlock") || "").toLowerCase();
4408
+ setBlock(current === tag ? "p" : tag);
4409
+ };
4410
+ const historyRef = React22.useRef([]);
4411
+ const historyIdxRef = React22.useRef(-1);
4412
+ const lastPushAtRef = React22.useRef(0);
4413
+ const isRestoringRef = React22.useRef(false);
4414
+ const caretOffset = (rootEl) => {
4415
+ const el = rootEl ?? editorRef.current;
4416
+ const sel = window.getSelection();
4417
+ if (!el || !sel || sel.rangeCount === 0 || !el.contains(sel.anchorNode)) return 0;
4418
+ const range = sel.getRangeAt(0).cloneRange();
4419
+ range.selectNodeContents(el);
4420
+ range.setEnd(sel.getRangeAt(0).endContainer, sel.getRangeAt(0).endOffset);
4421
+ return range.toString().length;
4422
+ };
4423
+ const setCaretOffset = (offset, rootEl) => {
4424
+ const el = rootEl ?? editorRef.current;
4425
+ if (!el) return;
4426
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
4427
+ let remaining = offset;
4428
+ let node = null;
4429
+ while (node = walker.nextNode()) {
4430
+ const len = node.textContent?.length ?? 0;
4431
+ if (remaining <= len) {
4432
+ const range = document.createRange();
4433
+ range.setStart(node, remaining);
4434
+ range.collapse(true);
4435
+ const sel = window.getSelection();
4436
+ sel?.removeAllRanges();
4437
+ sel?.addRange(range);
4438
+ return;
4439
+ }
4440
+ remaining -= len;
4441
+ }
4442
+ placeCaretAtEnd(el);
4443
+ };
4444
+ const pushHistory = (force = false) => {
4445
+ const el = editorRef.current;
4446
+ if (!el || isRestoringRef.current) return;
4447
+ const html = el.innerHTML;
4448
+ const hist = historyRef.current;
4449
+ const cur = hist[historyIdxRef.current];
4450
+ if (cur && cur.html === html) return;
4451
+ const now = Date.now();
4452
+ const coalesce = !force && cur && now - lastPushAtRef.current < 500;
4453
+ lastPushAtRef.current = now;
4454
+ if (coalesce) {
4455
+ hist[historyIdxRef.current] = { html, caret: caretOffset() };
4456
+ return;
4457
+ }
4458
+ hist.length = historyIdxRef.current + 1;
4459
+ hist.push({ html, caret: caretOffset() });
4460
+ if (hist.length > 200) hist.shift();
4461
+ historyIdxRef.current = hist.length - 1;
4462
+ };
4463
+ const applySnapshot = (snap) => {
4464
+ const el = editorRef.current;
4465
+ if (!el) return;
4466
+ isRestoringRef.current = true;
4467
+ el.innerHTML = snap.html;
4468
+ setCaretOffset(snap.caret);
4469
+ isRestoringRef.current = false;
4470
+ lastEmittedRef.current = el.innerHTML;
4471
+ onChange?.(el.innerHTML);
4472
+ updateEmpty();
4473
+ updateActiveFormats();
4474
+ };
4475
+ const undo = () => {
4476
+ pushHistory(true);
4477
+ if (historyIdxRef.current <= 0) return;
4478
+ historyIdxRef.current -= 1;
4479
+ applySnapshot(historyRef.current[historyIdxRef.current]);
4480
+ };
4481
+ const redo = () => {
4482
+ if (historyIdxRef.current >= historyRef.current.length - 1) return;
4483
+ historyIdxRef.current += 1;
4484
+ applySnapshot(historyRef.current[historyIdxRef.current]);
4485
+ };
4486
+ const langOf = (pre) => {
4487
+ const cls = Array.from(pre.classList).find((c) => c.startsWith("lang-"));
4488
+ return cls?.slice(5) ?? "plain";
4489
+ };
4490
+ const [codeLang, setCodeLang] = React22.useState("plain");
4491
+ const highlightRafRef = React22.useRef(0);
4492
+ const rehighlight = (pre) => {
4493
+ if (composingRef.current) return;
4494
+ const code = pre.querySelector("code");
4495
+ if (!code) return;
4496
+ const lang = langOf(pre);
4497
+ const text = code.textContent || "";
4498
+ const next = highlight(text, lang);
4499
+ if (code.innerHTML === next) return;
4500
+ const caret = caretOffset(code);
4501
+ isRestoringRef.current = true;
4502
+ code.innerHTML = next;
4503
+ setCaretOffset(caret, code);
4504
+ isRestoringRef.current = false;
4505
+ };
4506
+ const scheduleHighlight = () => {
4507
+ const pre = closestPre();
4508
+ if (!pre) return;
4509
+ cancelAnimationFrame(highlightRafRef.current);
4510
+ highlightRafRef.current = requestAnimationFrame(() => rehighlight(pre));
4511
+ };
4512
+ const changeCodeLang = (lang) => {
4513
+ const pre = closestPre();
4514
+ if (!pre) return;
4515
+ pushHistory(true);
4516
+ Array.from(pre.classList).filter((c) => c.startsWith("lang-")).forEach((c) => pre.classList.remove(c));
4517
+ if (lang !== "plain") pre.classList.add(`lang-${lang}`);
4518
+ setCodeLang(lang);
4519
+ rehighlight(pre);
4520
+ handleInput();
4521
+ };
4196
4522
  const closestPre = () => {
4197
4523
  const sel = window.getSelection();
4198
4524
  const node = sel?.anchorNode;
@@ -4201,6 +4527,7 @@ var Editor = (props) => {
4201
4527
  return el?.closest("pre") ?? null;
4202
4528
  };
4203
4529
  const insertCodeBlock = () => {
4530
+ pushHistory(true);
4204
4531
  const el = editorRef.current;
4205
4532
  const sel = window.getSelection();
4206
4533
  if (!el || !sel || sel.rangeCount === 0) return;
@@ -4283,7 +4610,167 @@ var Editor = (props) => {
4283
4610
  sel.addRange(r2);
4284
4611
  editorRef.current?.focus();
4285
4612
  };
4613
+ const clearFormat = () => {
4614
+ exec("removeFormat");
4615
+ exec("unlink");
4616
+ setBlock("p");
4617
+ };
4618
+ const indentList = (outdent) => {
4619
+ exec(outdent ? "outdent" : "indent");
4620
+ };
4621
+ const inListItem = () => {
4622
+ const sel = window.getSelection();
4623
+ const node = sel?.anchorNode;
4624
+ if (!node || !editorRef.current?.contains(node)) return false;
4625
+ const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
4626
+ return !!el?.closest("li");
4627
+ };
4628
+ const currentBlock = () => {
4629
+ const sel = window.getSelection();
4630
+ const node = sel?.anchorNode;
4631
+ const root = editorRef.current;
4632
+ if (!node || !root?.contains(node)) return null;
4633
+ let el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
4634
+ while (el && el !== root && !/^(P|H[1-6]|LI|BLOCKQUOTE|PRE|DIV|TD|TH)$/.test(el.tagName)) {
4635
+ el = el.parentElement;
4636
+ }
4637
+ return el && el !== root ? el : null;
4638
+ };
4639
+ const setAlign = (align) => {
4640
+ const block = currentBlock();
4641
+ if (!block) return;
4642
+ pushHistory(true);
4643
+ block.classList.remove("align-left", "align-center", "align-right");
4644
+ if (align !== "left") block.classList.add(`align-${align}`);
4645
+ handleInput();
4646
+ };
4647
+ const toggleChecklist = () => {
4648
+ pushHistory(true);
4649
+ const block = currentBlock();
4650
+ const list = block?.closest("ul");
4651
+ if (list?.classList.contains("checklist")) {
4652
+ list.classList.remove("checklist");
4653
+ list.querySelectorAll("li").forEach((li) => li.removeAttribute("data-checked"));
4654
+ handleInput();
4655
+ return;
4656
+ }
4657
+ if (list) {
4658
+ list.classList.add("checklist");
4659
+ list.querySelectorAll("li").forEach((li) => li.setAttribute("data-checked", "false"));
4660
+ handleInput();
4661
+ return;
4662
+ }
4663
+ exec("insertUnorderedList");
4664
+ const made = currentBlock()?.closest("ul");
4665
+ if (made) {
4666
+ made.classList.add("checklist");
4667
+ made.querySelectorAll("li").forEach((li) => li.setAttribute("data-checked", "false"));
4668
+ }
4669
+ handleInput();
4670
+ };
4671
+ const handleEditorClick = (e) => {
4672
+ if (readOnly) return;
4673
+ const target = e.target;
4674
+ const li = target.closest?.("li");
4675
+ if (!li || !li.closest("ul.checklist")) return;
4676
+ const rect = li.getBoundingClientRect();
4677
+ if (e.clientX - rect.left > 20) return;
4678
+ e.preventDefault();
4679
+ pushHistory(true);
4680
+ li.setAttribute("data-checked", li.getAttribute("data-checked") === "true" ? "false" : "true");
4681
+ handleInput();
4682
+ };
4683
+ const toggleHighlight = () => {
4684
+ const sel = window.getSelection();
4685
+ if (!sel || sel.rangeCount === 0 || sel.isCollapsed) return;
4686
+ pushHistory(true);
4687
+ const range = sel.getRangeAt(0);
4688
+ const node = sel.anchorNode;
4689
+ const el = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement;
4690
+ const existing = el?.closest("mark");
4691
+ if (existing) {
4692
+ const parent = existing.parentNode;
4693
+ while (existing.firstChild) parent?.insertBefore(existing.firstChild, existing);
4694
+ parent?.removeChild(existing);
4695
+ handleInput();
4696
+ return;
4697
+ }
4698
+ const mark = document.createElement("mark");
4699
+ try {
4700
+ range.surroundContents(mark);
4701
+ } catch {
4702
+ const frag = range.extractContents();
4703
+ mark.appendChild(frag);
4704
+ range.insertNode(mark);
4705
+ }
4706
+ handleInput();
4707
+ };
4708
+ const insertTable = (cols = 3, rows = 2) => {
4709
+ const sel = window.getSelection();
4710
+ if (!sel || sel.rangeCount === 0) return;
4711
+ pushHistory(true);
4712
+ const table = document.createElement("table");
4713
+ const thead = document.createElement("thead");
4714
+ const headRow = document.createElement("tr");
4715
+ for (let c = 0; c < cols; c++) {
4716
+ const th = document.createElement("th");
4717
+ th.innerHTML = "<br>";
4718
+ headRow.appendChild(th);
4719
+ }
4720
+ thead.appendChild(headRow);
4721
+ table.appendChild(thead);
4722
+ const tbody = document.createElement("tbody");
4723
+ for (let r2 = 0; r2 < rows; r2++) {
4724
+ const tr = document.createElement("tr");
4725
+ for (let c = 0; c < cols; c++) {
4726
+ const td = document.createElement("td");
4727
+ td.innerHTML = "<br>";
4728
+ tr.appendChild(td);
4729
+ }
4730
+ tbody.appendChild(tr);
4731
+ }
4732
+ table.appendChild(tbody);
4733
+ const range = sel.getRangeAt(0);
4734
+ range.deleteContents();
4735
+ range.insertNode(table);
4736
+ ensureParagraphAfter(table);
4737
+ const first = table.querySelector("th");
4738
+ if (first) placeCaretAtEnd(first);
4739
+ handleInput();
4740
+ };
4741
+ const moveTableCell = (backwards) => {
4742
+ const block = currentBlock();
4743
+ const cell = block?.closest("td, th");
4744
+ if (!cell) return false;
4745
+ const table = cell.closest("table");
4746
+ if (!table) return false;
4747
+ const cells = Array.from(table.querySelectorAll("td, th"));
4748
+ const i = cells.indexOf(cell);
4749
+ const next = cells[i + (backwards ? -1 : 1)];
4750
+ if (next) {
4751
+ placeCaretAtEnd(next);
4752
+ return true;
4753
+ }
4754
+ if (!backwards) {
4755
+ pushHistory(true);
4756
+ const lastRow = table.querySelector("tbody")?.lastElementChild;
4757
+ if (lastRow) {
4758
+ const tr = document.createElement("tr");
4759
+ for (let c = 0; c < lastRow.children.length; c++) {
4760
+ const td = document.createElement("td");
4761
+ td.innerHTML = "<br>";
4762
+ tr.appendChild(td);
4763
+ }
4764
+ lastRow.after(tr);
4765
+ placeCaretAtEnd(tr.firstElementChild);
4766
+ handleInput();
4767
+ return true;
4768
+ }
4769
+ }
4770
+ return false;
4771
+ };
4286
4772
  const insertDivider = () => {
4773
+ pushHistory(true);
4287
4774
  exec("insertHorizontalRule");
4288
4775
  const el = editorRef.current;
4289
4776
  if (el && el.lastElementChild?.tagName === "HR") {
@@ -4376,8 +4863,14 @@ var Editor = (props) => {
4376
4863
  case "paragraph":
4377
4864
  setBlock("p");
4378
4865
  break;
4379
- case "heading":
4380
- setBlock("h2");
4866
+ case "heading-1":
4867
+ toggleBlock("h1");
4868
+ break;
4869
+ case "heading-2":
4870
+ toggleBlock("h2");
4871
+ break;
4872
+ case "heading-3":
4873
+ toggleBlock("h3");
4381
4874
  break;
4382
4875
  case "list":
4383
4876
  exec("insertUnorderedList");
@@ -4470,6 +4963,8 @@ var Editor = (props) => {
4470
4963
  if (!el) return;
4471
4964
  const html = el.innerHTML;
4472
4965
  lastEmittedRef.current = html;
4966
+ pushHistory();
4967
+ scheduleHighlight();
4473
4968
  onChange?.(html);
4474
4969
  updateEmpty();
4475
4970
  updateActiveFormats();
@@ -4523,6 +5018,15 @@ var Editor = (props) => {
4523
5018
  return;
4524
5019
  }
4525
5020
  }
5021
+ if (e.key === "Tab" && moveTableCell(e.shiftKey)) {
5022
+ e.preventDefault();
5023
+ return;
5024
+ }
5025
+ if (e.key === "Tab" && inListItem()) {
5026
+ e.preventDefault();
5027
+ indentList(e.shiftKey);
5028
+ return;
5029
+ }
4526
5030
  if (slashOpen) {
4527
5031
  if (e.key === "ArrowDown") {
4528
5032
  e.preventDefault();
@@ -4546,6 +5050,21 @@ var Editor = (props) => {
4546
5050
  return;
4547
5051
  }
4548
5052
  }
5053
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "z") {
5054
+ e.preventDefault();
5055
+ if (e.shiftKey) redo();
5056
+ else undo();
5057
+ return;
5058
+ }
5059
+ if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "v") {
5060
+ plainPasteRef.current = true;
5061
+ return;
5062
+ }
5063
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "y") {
5064
+ e.preventDefault();
5065
+ redo();
5066
+ return;
5067
+ }
4549
5068
  if ((e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
4550
5069
  const k = e.key.toLowerCase();
4551
5070
  if (k === "b") {
@@ -4579,10 +5098,16 @@ var Editor = (props) => {
4579
5098
  }
4580
5099
  }
4581
5100
  };
5101
+ const plainPasteRef = React22.useRef(false);
4582
5102
  const handlePaste = (e) => {
4583
5103
  if (readOnly) return;
4584
5104
  e.preventDefault();
4585
5105
  const text = e.clipboardData.getData("text/plain");
5106
+ if (plainPasteRef.current) {
5107
+ plainPasteRef.current = false;
5108
+ insertTextAtCaret(text);
5109
+ return;
5110
+ }
4586
5111
  if (closestPre()) {
4587
5112
  insertTextAtCaret(text);
4588
5113
  return;
@@ -4609,9 +5134,11 @@ var Editor = (props) => {
4609
5134
  };
4610
5135
  const handleSelectionUpdate = () => {
4611
5136
  updateActiveFormats();
5137
+ const pre = closestPre();
5138
+ setCodeLang(pre ? langOf(pre) : "plain");
4612
5139
  };
4613
5140
  const onToolbarAction = (item) => {
4614
- if (closestPre() && item !== "code-block") return;
5141
+ if (closestPre() && !["code-block", "undo", "redo"].includes(item)) return;
4615
5142
  switch (item) {
4616
5143
  case "bold":
4617
5144
  exec("bold");
@@ -4640,8 +5167,14 @@ var Editor = (props) => {
4640
5167
  handleInput();
4641
5168
  break;
4642
5169
  }
4643
- case "heading":
4644
- setBlock("h2");
5170
+ case "heading-1":
5171
+ toggleBlock("h1");
5172
+ break;
5173
+ case "heading-2":
5174
+ toggleBlock("h2");
5175
+ break;
5176
+ case "heading-3":
5177
+ toggleBlock("h3");
4645
5178
  break;
4646
5179
  case "list":
4647
5180
  exec("insertUnorderedList");
@@ -4664,6 +5197,39 @@ var Editor = (props) => {
4664
5197
  case "divider":
4665
5198
  insertDivider();
4666
5199
  break;
5200
+ case "checklist":
5201
+ toggleChecklist();
5202
+ break;
5203
+ case "align-left":
5204
+ setAlign("left");
5205
+ break;
5206
+ case "align-center":
5207
+ setAlign("center");
5208
+ break;
5209
+ case "align-right":
5210
+ setAlign("right");
5211
+ break;
5212
+ case "highlight":
5213
+ toggleHighlight();
5214
+ break;
5215
+ case "superscript":
5216
+ exec("superscript");
5217
+ break;
5218
+ case "subscript":
5219
+ exec("subscript");
5220
+ break;
5221
+ case "table":
5222
+ insertTable();
5223
+ break;
5224
+ case "clear-format":
5225
+ clearFormat();
5226
+ break;
5227
+ case "undo":
5228
+ undo();
5229
+ break;
5230
+ case "redo":
5231
+ redo();
5232
+ break;
4667
5233
  }
4668
5234
  };
4669
5235
  return /* @__PURE__ */ jsxs207("div", { className: clsx_default("lib-xplat-editor", isFocused && "focused", readOnly && "readonly"), children: [
@@ -4672,7 +5238,10 @@ var Editor = (props) => {
4672
5238
  {
4673
5239
  items: toolbar,
4674
5240
  active: activeFormats,
4675
- onAction: onToolbarAction
5241
+ onAction: onToolbarAction,
5242
+ codeLang,
5243
+ inCodeBlock: activeFormats.has("block:pre"),
5244
+ onCodeLangChange: changeCodeLang
4676
5245
  }
4677
5246
  ),
4678
5247
  /* @__PURE__ */ jsxs207("div", { className: "lib-xplat-editor__content", style: { minHeight }, children: [
@@ -4690,6 +5259,7 @@ var Editor = (props) => {
4690
5259
  className: "lib-xplat-editor__editable",
4691
5260
  onInput: handleInput,
4692
5261
  onKeyDown: handleKeyDown,
5262
+ onClick: handleEditorClick,
4693
5263
  onKeyUp: handleSelectionUpdate,
4694
5264
  onMouseUp: handleSelectionUpdate,
4695
5265
  onPaste: handlePaste,
@@ -4731,14 +5301,27 @@ var TOOLBAR_LABEL = {
4731
5301
  underline: "U",
4732
5302
  strikethrough: "S",
4733
5303
  code: "<>",
4734
- heading: "H",
5304
+ "heading-1": "H1",
5305
+ "heading-2": "H2",
5306
+ "heading-3": "H3",
4735
5307
  list: "\u2022",
4736
5308
  "ordered-list": "1.",
4737
5309
  blockquote: "\u275D",
4738
5310
  "code-block": "[ ]",
4739
5311
  link: "\u{1F517}",
4740
5312
  image: "\u{1F5BC}",
4741
- divider: "\u2014"
5313
+ divider: "\u2014",
5314
+ checklist: "\u2611",
5315
+ "align-left": "\u21E4",
5316
+ "align-center": "\u2194",
5317
+ "align-right": "\u21E5",
5318
+ highlight: "\u{1F58D}",
5319
+ superscript: "x\xB2",
5320
+ subscript: "x\u2082",
5321
+ table: "\u25A6",
5322
+ "clear-format": "\u232B",
5323
+ undo: "\u21B6",
5324
+ redo: "\u21B7"
4742
5325
  };
4743
5326
  var TOOLBAR_TITLE = {
4744
5327
  bold: "\uAD75\uAC8C (\u2318B)",
@@ -4746,36 +5329,74 @@ var TOOLBAR_TITLE = {
4746
5329
  underline: "\uBC11\uC904 (\u2318U)",
4747
5330
  strikethrough: "\uCDE8\uC18C\uC120",
4748
5331
  code: "\uC778\uB77C\uC778 \uCF54\uB4DC",
4749
- heading: "\uC81C\uBAA9",
5332
+ "heading-1": "\uC81C\uBAA9 1",
5333
+ "heading-2": "\uC81C\uBAA9 2",
5334
+ "heading-3": "\uC81C\uBAA9 3",
4750
5335
  list: "\uAE00\uBA38\uB9AC \uAE30\uD638 \uBAA9\uB85D",
4751
5336
  "ordered-list": "\uBC88\uD638 \uB9E4\uAE30\uAE30 \uBAA9\uB85D",
4752
5337
  blockquote: "\uC778\uC6A9",
4753
5338
  "code-block": "\uCF54\uB4DC \uBE14\uB85D",
4754
5339
  link: "\uB9C1\uD06C (\u2318K)",
4755
5340
  image: "\uC774\uBBF8\uC9C0",
4756
- divider: "\uAD6C\uBD84\uC120"
5341
+ divider: "\uAD6C\uBD84\uC120",
5342
+ checklist: "\uCCB4\uD06C\uB9AC\uC2A4\uD2B8",
5343
+ "align-left": "\uC67C\uCABD \uC815\uB82C",
5344
+ "align-center": "\uAC00\uC6B4\uB370 \uC815\uB82C",
5345
+ "align-right": "\uC624\uB978\uCABD \uC815\uB82C",
5346
+ highlight: "\uD615\uAD11\uD39C",
5347
+ superscript: "\uC704 \uCCA8\uC790",
5348
+ subscript: "\uC544\uB798 \uCCA8\uC790",
5349
+ table: "\uD45C \uC0BD\uC785",
5350
+ "clear-format": "\uC11C\uC2DD \uC9C0\uC6B0\uAE30",
5351
+ undo: "\uB418\uB3CC\uB9AC\uAE30 (\u2318Z)",
5352
+ redo: "\uB2E4\uC2DC\uD558\uAE30 (\u2318\u21E7Z)"
4757
5353
  };
4758
5354
  var isActive = (item, active) => {
4759
- if (item === "heading") return active.has("block:h1") || active.has("block:h2") || active.has("block:h3");
5355
+ if (item === "heading-1") return active.has("block:h1");
5356
+ if (item === "heading-2") return active.has("block:h2");
5357
+ if (item === "heading-3") return active.has("block:h3");
4760
5358
  if (item === "blockquote") return active.has("block:blockquote");
4761
5359
  if (item === "code-block") return active.has("block:pre");
5360
+ if (item === "align-left") return active.has("align:left");
5361
+ if (item === "align-center") return active.has("align:center");
5362
+ if (item === "align-right") return active.has("align:right");
4762
5363
  return active.has(item);
4763
5364
  };
4764
- var EditorToolbar = ({ items, active, onAction }) => {
4765
- return /* @__PURE__ */ jsx323("div", { className: "lib-xplat-editor__toolbar", children: items.map((item) => /* @__PURE__ */ jsx323(
4766
- "button",
4767
- {
4768
- type: "button",
4769
- className: clsx_default("lib-xplat-editor__toolbar-btn", isActive(item, active) && "active"),
4770
- title: TOOLBAR_TITLE[item],
4771
- onMouseDown: (e) => {
4772
- e.preventDefault();
4773
- onAction(item);
5365
+ var EditorToolbar = ({
5366
+ items,
5367
+ active,
5368
+ onAction,
5369
+ codeLang,
5370
+ inCodeBlock,
5371
+ onCodeLangChange
5372
+ }) => {
5373
+ return /* @__PURE__ */ jsxs207("div", { className: "lib-xplat-editor__toolbar", children: [
5374
+ items.map((item) => /* @__PURE__ */ jsx323(
5375
+ "button",
5376
+ {
5377
+ type: "button",
5378
+ className: clsx_default("lib-xplat-editor__toolbar-btn", isActive(item, active) && "active"),
5379
+ title: TOOLBAR_TITLE[item],
5380
+ onMouseDown: (e) => {
5381
+ e.preventDefault();
5382
+ onAction(item);
5383
+ },
5384
+ children: TOOLBAR_LABEL[item]
4774
5385
  },
4775
- children: TOOLBAR_LABEL[item]
4776
- },
4777
- item
4778
- )) });
5386
+ item
5387
+ )),
5388
+ inCodeBlock && /* @__PURE__ */ jsx323(
5389
+ "select",
5390
+ {
5391
+ className: "lib-xplat-editor__lang",
5392
+ value: codeLang,
5393
+ "aria-label": "\uCF54\uB4DC \uC5B8\uC5B4",
5394
+ onMouseDown: (e) => e.stopPropagation(),
5395
+ onChange: (e) => onCodeLangChange(e.target.value),
5396
+ children: CODE_LANGUAGES.map((l) => /* @__PURE__ */ jsx323("option", { value: l.value, children: l.label }, l.value))
5397
+ }
5398
+ )
5399
+ ] });
4779
5400
  };
4780
5401
  var SlashMenu = ({ position, items, activeIndex, onSelect, onClose }) => {
4781
5402
  React22.useEffect(() => {
@@ -4879,10 +5500,13 @@ var FileUpload = (props) => {
4879
5500
  multiple = false,
4880
5501
  maxSize,
4881
5502
  onChange,
4882
- label = "\uD30C\uC77C\uC744 \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \uD074\uB9AD\uD558\uC5EC \uC5C5\uB85C\uB4DC",
5503
+ placeholder = "\uD30C\uC77C\uC744 \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \uD074\uB9AD\uD558\uC5EC \uC5C5\uB85C\uB4DC",
4883
5504
  description,
4884
- validations
5505
+ validations,
5506
+ label,
5507
+ required
4885
5508
  } = props;
5509
+ const fieldId = React23.useId();
4886
5510
  const [isDragOver, setIsDragOver] = React23.useState(false);
4887
5511
  const inputRef = React23.useRef(null);
4888
5512
  const handleFiles = (fileList) => {
@@ -4927,6 +5551,7 @@ var FileUpload = (props) => {
4927
5551
  "input",
4928
5552
  {
4929
5553
  ref: inputRef,
5554
+ id: fieldId,
4930
5555
  type: "file",
4931
5556
  accept,
4932
5557
  multiple,
@@ -4935,12 +5560,12 @@ var FileUpload = (props) => {
4935
5560
  }
4936
5561
  ),
4937
5562
  /* @__PURE__ */ jsx325("div", { className: "upload-icon", children: /* @__PURE__ */ jsx325(UploadIcon_default, {}) }),
4938
- /* @__PURE__ */ jsx325("p", { className: "upload-label", children: label }),
5563
+ /* @__PURE__ */ jsx325("p", { className: "upload-label", children: placeholder }),
4939
5564
  description && /* @__PURE__ */ jsx325("p", { className: "upload-description", children: description })
4940
5565
  ]
4941
5566
  }
4942
5567
  );
4943
- return /* @__PURE__ */ jsx325(Field, { validations, children: control });
5568
+ return /* @__PURE__ */ jsx325(Field, { label, required, htmlFor: fieldId, validations, children: control });
4944
5569
  };
4945
5570
  FileUpload.displayName = "FileUpload";
4946
5571
  var FileUpload_default = FileUpload;
@@ -5083,7 +5708,8 @@ var HtmlTypeWriter_default = HtmlTypeWriter;
5083
5708
  import React26 from "react";
5084
5709
  import { jsx as jsx327, jsxs as jsxs210 } from "react/jsx-runtime";
5085
5710
  var ImageSelector = (props) => {
5086
- const { value, label, onChange, validations } = props;
5711
+ const { value, placeholder, onChange, validations, label, required } = props;
5712
+ const fieldId = React26.useId();
5087
5713
  const [previewUrl, setPreviewUrl] = React26.useState();
5088
5714
  React26.useEffect(() => {
5089
5715
  if (!value) {
@@ -5113,7 +5739,7 @@ var ImageSelector = (props) => {
5113
5739
  {
5114
5740
  type: "file",
5115
5741
  accept: "image/*",
5116
- id: "image-input",
5742
+ id: fieldId,
5117
5743
  onChange: handleFileChange,
5118
5744
  ref: inputRef,
5119
5745
  "aria-invalid": hasError(validations) || void 0
@@ -5125,10 +5751,10 @@ var ImageSelector = (props) => {
5125
5751
  ] }),
5126
5752
  /* @__PURE__ */ jsx327("div", { className: "content", children: previewUrl ? /* @__PURE__ */ jsx327("img", { src: previewUrl, alt: "preview" }) : /* @__PURE__ */ jsxs210("div", { className: "skeleton", onClick: handleOpenFileDialog, children: [
5127
5753
  /* @__PURE__ */ jsx327("div", { className: "icon-wrapper", children: /* @__PURE__ */ jsx327(ImageIcon_default, {}) }),
5128
- /* @__PURE__ */ jsx327("div", { className: "label", children: label || "\uC774\uBBF8\uC9C0 \uCD94\uAC00\uD558\uAE30" })
5754
+ /* @__PURE__ */ jsx327("div", { className: "label", children: placeholder || "\uC774\uBBF8\uC9C0 \uCD94\uAC00\uD558\uAE30" })
5129
5755
  ] }) })
5130
5756
  ] });
5131
- return /* @__PURE__ */ jsx327(Field, { validations, children: control });
5757
+ return /* @__PURE__ */ jsx327(Field, { label, required, htmlFor: fieldId, validations, children: control });
5132
5758
  };
5133
5759
  ImageSelector.displayName = "ImageSelector";
5134
5760
  var ImageSelector_default = ImageSelector;
@@ -5320,6 +5946,7 @@ var PopOver = (props) => {
5320
5946
  className: clsx_default(
5321
5947
  "lib-xplat-pop-over-content",
5322
5948
  position.direction,
5949
+ position.repositioning && "repositioning",
5323
5950
  eventTrigger && "visible"
5324
5951
  ),
5325
5952
  ref: popRef,
@@ -5666,7 +6293,7 @@ var SelectRoot = (props) => {
5666
6293
  mounted && /* @__PURE__ */ jsx337(Portal_default, { children: /* @__PURE__ */ jsxs216(
5667
6294
  "div",
5668
6295
  {
5669
- className: clsx_default("lib-xplat-select-content", size, position.direction, stateClass),
6296
+ className: clsx_default("lib-xplat-select-content", size, position.direction, stateClass, position.repositioning && "repositioning"),
5670
6297
  ref: contentRef,
5671
6298
  style: { ...position.position, width: triggerRef.current?.offsetWidth },
5672
6299
  role: "listbox",
@@ -6559,7 +7186,7 @@ var TimePicker = (props) => {
6559
7186
  "div",
6560
7187
  {
6561
7188
  ref: contentRef,
6562
- className: clsx_default("lib-xplat-time-picker-content", size, visible ? "enter" : "exit"),
7189
+ className: clsx_default("lib-xplat-time-picker-content", size, visible ? "enter" : "exit", position.repositioning && "repositioning"),
6563
7190
  style: { ...position.position, width: triggerRef.current?.offsetWidth },
6564
7191
  role: "listbox",
6565
7192
  children: options.map((t) => /* @__PURE__ */ jsx351(