rich-html-editor 0.2.1 → 1.0.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.
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ var init_events = __esm({
91
91
  });
92
92
 
93
93
  // src/core/constants.ts
94
- var TOOLBAR_ID, STYLE_ID, CLASS_EDITABLE, CLASS_ACTIVE, DEFAULT_MAX_STACK, TOOLBAR_BG, TOOLBAR_BORDER, BUTTON_BORDER, BUTTON_ACTIVE_BG, BUTTON_BG, BUTTON_COLOR, INFO_COLOR, HOVER_OUTLINE, ACTIVE_OUTLINE, LABEL_BOLD, LABEL_ITALIC, LABEL_UNDERLINE, LABEL_STRIKETHROUGH, LABEL_UNDO, LABEL_REDO, LABEL_LINK, LABEL_ALIGN_LEFT, LABEL_ALIGN_CENTER, LABEL_ALIGN_RIGHT, FONT_OPTIONS, SIZE_OPTIONS, FORMAT_OPTIONS;
94
+ var TOOLBAR_ID, STYLE_ID, CLASS_EDITABLE, CLASS_ACTIVE, DEFAULT_MAX_STACK, TOOLBAR_BG, TOOLBAR_BORDER, BUTTON_BORDER, BUTTON_ACTIVE_BG, BUTTON_BG, BUTTON_COLOR, INFO_COLOR, HOVER_OUTLINE, ACTIVE_OUTLINE, LABEL_BOLD, LABEL_ITALIC, LABEL_UNDERLINE, LABEL_STRIKETHROUGH, LABEL_UNDO, LABEL_REDO, LABEL_LINK, LABEL_UNORDERED_LIST, LABEL_ORDERED_LIST, LABEL_ALIGN_LEFT, LABEL_ALIGN_CENTER, LABEL_ALIGN_RIGHT, FONT_OPTIONS, SIZE_OPTIONS, FORMAT_OPTIONS;
95
95
  var init_constants = __esm({
96
96
  "src/core/constants.ts"() {
97
97
  "use strict";
@@ -116,6 +116,26 @@ var init_constants = __esm({
116
116
  LABEL_UNDO = "\u21BA";
117
117
  LABEL_REDO = "\u21BB";
118
118
  LABEL_LINK = "\u{1F517}";
119
+ LABEL_UNORDERED_LIST = `
120
+ <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
121
+ <circle cx="3" cy="4" r="1" fill="currentColor" />
122
+ <rect x="6" y="3" width="9" height="2" rx="0.5" fill="currentColor" />
123
+ <circle cx="3" cy="8" r="1" fill="currentColor" />
124
+ <rect x="6" y="7" width="9" height="2" rx="0.5" fill="currentColor" />
125
+ <circle cx="3" cy="12" r="1" fill="currentColor" />
126
+ <rect x="6" y="11" width="9" height="2" rx="0.5" fill="currentColor" />
127
+ </svg>
128
+ `;
129
+ LABEL_ORDERED_LIST = `
130
+ <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
131
+ <text x="1" y="4" font-size="4" fill="currentColor">1.</text>
132
+ <rect x="6" y="3" width="9" height="2" rx="0.5" fill="currentColor" />
133
+ <text x="1" y="8" font-size="4" fill="currentColor">2.</text>
134
+ <rect x="6" y="7" width="9" height="2" rx="0.5" fill="currentColor" />
135
+ <text x="1" y="12" font-size="4" fill="currentColor">3.</text>
136
+ <rect x="6" y="11" width="9" height="2" rx="0.5" fill="currentColor" />
137
+ </svg>
138
+ `;
119
139
  LABEL_ALIGN_LEFT = `
120
140
  <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
121
141
  <rect x="1" y="2" width="10" height="2" rx="0.5" fill="currentColor" />
@@ -761,219 +781,76 @@ function injectStyles(doc) {
761
781
  styleEl.textContent = css;
762
782
  }
763
783
 
764
- // src/toolbar/toolbar.ts
784
+ // src/toolbar/render.ts
765
785
  init_constants();
766
- function injectToolbar(doc, options) {
767
- const existing = doc.getElementById(TOOLBAR_ID);
768
- if (existing) existing.remove();
769
- const toolbar = doc.createElement("div");
770
- toolbar.id = TOOLBAR_ID;
771
- toolbar.setAttribute("role", "toolbar");
772
- toolbar.setAttribute("aria-label", "Rich text editor toolbar");
773
- function makeButton(label, title, command, value, isActive, disabled) {
774
- const btn = doc.createElement("button");
775
- btn.type = "button";
776
- if (label && label.trim().startsWith("<")) {
777
- btn.innerHTML = label;
778
- } else {
779
- btn.textContent = label;
786
+
787
+ // src/toolbar/color.ts
788
+ function makeColorInput(doc, options, title, command, initialColor) {
789
+ const input = doc.createElement("input");
790
+ input.type = "color";
791
+ input.className = "toolbar-color-input";
792
+ const wrapper = doc.createElement("label");
793
+ wrapper.className = "color-label";
794
+ wrapper.appendChild(doc.createTextNode(title + " "));
795
+ wrapper.appendChild(input);
796
+ let savedRange = null;
797
+ input.addEventListener("pointerdown", () => {
798
+ const s = doc.getSelection();
799
+ if (s && s.rangeCount) savedRange = s.getRangeAt(0).cloneRange();
800
+ });
801
+ input.onchange = (e) => {
802
+ try {
803
+ const s = doc.getSelection();
804
+ if (savedRange && s) {
805
+ s.removeAllRanges();
806
+ s.addRange(savedRange);
807
+ }
808
+ } catch (err) {
780
809
  }
781
- btn.title = title;
782
- btn.setAttribute("aria-label", title);
783
- if (typeof isActive !== "undefined")
784
- btn.setAttribute("aria-pressed", String(!!isActive));
785
- btn.tabIndex = 0;
786
- if (disabled) btn.disabled = true;
787
- btn.onclick = () => options.onCommand(command, value);
788
- btn.addEventListener("keydown", (ev) => {
789
- if (ev.key === "Enter" || ev.key === " ") {
790
- ev.preventDefault();
791
- btn.click();
810
+ options.onCommand(command, e.target.value);
811
+ savedRange = null;
812
+ };
813
+ function rgbToHex(input2) {
814
+ if (!input2) return null;
815
+ const v = input2.trim();
816
+ if (v.startsWith("#")) {
817
+ if (v.length === 4) {
818
+ return ("#" + v[1] + v[1] + v[2] + v[2] + v[3] + v[3]).toLowerCase();
792
819
  }
793
- });
794
- return btn;
795
- }
796
- function makeSelect(title, command, optionsList, initialValue) {
797
- const select = doc.createElement("select");
798
- select.title = title;
799
- select.setAttribute("aria-label", title);
800
- select.appendChild(new Option(title, "", true, true));
801
- for (const opt of optionsList) {
802
- select.appendChild(new Option(opt.label, opt.value));
820
+ return v.toLowerCase();
803
821
  }
804
- try {
805
- if (initialValue) select.value = initialValue;
806
- } catch (e) {
822
+ const rgbMatch = v.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
823
+ if (rgbMatch) {
824
+ const r = Number(rgbMatch[1]);
825
+ const g = Number(rgbMatch[2]);
826
+ const b = Number(rgbMatch[3]);
827
+ const hex = "#" + [r, g, b].map((n) => n.toString(16).padStart(2, "0")).join("").toLowerCase();
828
+ return hex;
807
829
  }
808
- select.onchange = (e) => {
809
- const val = e.target.value;
810
- options.onCommand(command, val);
811
- select.selectedIndex = 0;
812
- };
813
- return select;
830
+ return null;
814
831
  }
815
- function makeColorInput(title, command, initialColor) {
816
- const input = doc.createElement("input");
817
- input.type = "color";
818
- input.className = "toolbar-color-input";
819
- const wrapper = doc.createElement("label");
820
- wrapper.className = "color-label";
821
- wrapper.appendChild(doc.createTextNode(title + " "));
822
- wrapper.appendChild(input);
823
- let savedRange = null;
824
- input.addEventListener("pointerdown", () => {
825
- const s = doc.getSelection();
826
- if (s && s.rangeCount) savedRange = s.getRangeAt(0).cloneRange();
827
- });
828
- input.onchange = (e) => {
829
- try {
830
- const s = doc.getSelection();
831
- if (savedRange && s) {
832
- s.removeAllRanges();
833
- s.addRange(savedRange);
834
- }
835
- } catch (err) {
836
- }
837
- options.onCommand(command, e.target.value);
838
- savedRange = null;
839
- };
840
- function rgbToHex(input2) {
841
- if (!input2) return null;
842
- const v = input2.trim();
843
- if (v.startsWith("#")) {
844
- if (v.length === 4) {
845
- return ("#" + v[1] + v[1] + v[2] + v[2] + v[3] + v[3]).toLowerCase();
846
- }
847
- return v.toLowerCase();
848
- }
849
- const rgbMatch = v.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
850
- if (rgbMatch) {
851
- const r = Number(rgbMatch[1]);
852
- const g = Number(rgbMatch[2]);
853
- const b = Number(rgbMatch[3]);
854
- const hex = "#" + [r, g, b].map((n) => n.toString(16).padStart(2, "0")).join("").toLowerCase();
855
- return hex;
832
+ const setColor = (val) => {
833
+ if (!val) return;
834
+ const hex = rgbToHex(val) || val;
835
+ try {
836
+ if (hex && hex.startsWith("#") && input.value !== hex) {
837
+ input.value = hex;
856
838
  }
857
- return null;
839
+ } catch (e) {
858
840
  }
859
- const setColor = (val) => {
860
- if (!val) return;
861
- const hex = rgbToHex(val) || val;
862
- try {
863
- if (hex && hex.startsWith("#") && input.value !== hex) {
864
- input.value = hex;
865
- }
866
- } catch (e) {
867
- }
868
- };
869
- if (initialColor) setColor(initialColor);
870
- input.addEventListener("input", (e) => {
871
- const val = e.target.value;
872
- setColor(val);
873
- });
874
- input.title = title;
875
- input.setAttribute("aria-label", title);
876
- return wrapper;
877
- }
878
- const format = options.getFormatState();
879
- function makeGroup() {
880
- const g = doc.createElement("div");
881
- g.className = "toolbar-group";
882
- return g;
883
- }
884
- function makeSep() {
885
- const s = doc.createElement("div");
886
- s.className = "toolbar-sep";
887
- return s;
888
- }
889
- const undoBtn = makeButton(
890
- LABEL_UNDO,
891
- "Undo",
892
- "undo",
893
- void 0,
894
- false,
895
- !options.canUndo()
896
- );
897
- undoBtn.onclick = () => options.onUndo();
898
- const redoBtn = makeButton(
899
- LABEL_REDO,
900
- "Redo",
901
- "redo",
902
- void 0,
903
- false,
904
- !options.canRedo()
905
- );
906
- redoBtn.onclick = () => options.onRedo();
907
- const grp1 = makeGroup();
908
- grp1.appendChild(undoBtn);
909
- grp1.appendChild(redoBtn);
910
- toolbar.appendChild(grp1);
911
- toolbar.appendChild(makeSep());
912
- const grp2 = makeGroup();
913
- grp2.className = "toolbar-group collapse-on-small";
914
- grp2.appendChild(
915
- makeSelect(
916
- "Format",
917
- "formatBlock",
918
- FORMAT_OPTIONS,
919
- format.formatBlock
920
- )
921
- );
922
- grp2.appendChild(
923
- makeSelect("Font", "fontName", FONT_OPTIONS, format.fontName)
924
- );
925
- grp2.appendChild(
926
- makeSelect("Size", "fontSize", SIZE_OPTIONS, format.fontSize)
927
- );
928
- toolbar.appendChild(grp2);
929
- toolbar.appendChild(makeSep());
930
- const grp3 = makeGroup();
931
- grp3.appendChild(
932
- makeButton(LABEL_BOLD, "Bold", "bold", void 0, format.bold)
933
- );
934
- grp3.appendChild(
935
- makeButton(LABEL_ITALIC, "Italic", "italic", void 0, format.italic)
936
- );
937
- grp3.appendChild(
938
- makeButton(
939
- LABEL_UNDERLINE,
940
- "Underline",
941
- "underline",
942
- void 0,
943
- format.underline
944
- )
945
- );
946
- grp3.appendChild(makeButton(LABEL_STRIKETHROUGH, "Strikethrough", "strike"));
947
- toolbar.appendChild(grp3);
948
- toolbar.appendChild(makeSep());
949
- const grp4 = makeGroup();
950
- grp4.appendChild(makeButton(LABEL_ALIGN_LEFT, "Align left", "align", "left"));
951
- grp4.appendChild(
952
- makeButton(LABEL_ALIGN_CENTER, "Align center", "align", "center")
953
- );
954
- grp4.appendChild(
955
- makeButton(LABEL_ALIGN_RIGHT, "Align right", "align", "right")
956
- );
957
- toolbar.appendChild(grp4);
958
- toolbar.appendChild(makeSep());
959
- const grp5 = makeGroup();
960
- grp5.className = "toolbar-group collapse-on-small";
961
- grp5.appendChild(
962
- makeColorInput("Text color", "foreColor", format.foreColor)
963
- );
964
- grp5.appendChild(
965
- makeColorInput(
966
- "Highlight color",
967
- "hiliteColor",
968
- format.hiliteColor
969
- )
970
- );
971
- toolbar.appendChild(grp5);
972
- toolbar.appendChild(makeSep());
973
- const grp6 = makeGroup();
974
- grp6.className = "toolbar-group collapse-on-small";
975
- grp6.appendChild(makeButton(LABEL_LINK, "Insert link", "link"));
976
- toolbar.appendChild(grp6);
841
+ };
842
+ if (initialColor) setColor(initialColor);
843
+ input.addEventListener("input", (e) => {
844
+ const val = e.target.value;
845
+ setColor(val);
846
+ });
847
+ input.title = title;
848
+ input.setAttribute("aria-label", title);
849
+ return wrapper;
850
+ }
851
+
852
+ // src/toolbar/overflow.ts
853
+ function setupOverflow(doc, toolbar, options, format, helpers) {
977
854
  const overflowBtn = doc.createElement("button");
978
855
  overflowBtn.type = "button";
979
856
  overflowBtn.className = "toolbar-overflow-btn";
@@ -1000,7 +877,7 @@ function injectToolbar(doc, options) {
1000
877
  overflowBtn.setAttribute("aria-expanded", "false");
1001
878
  overflowBtn.focus();
1002
879
  }
1003
- overflowBtn.addEventListener("click", (e) => {
880
+ overflowBtn.addEventListener("click", () => {
1004
881
  if (overflowMenu.hidden) openOverflow();
1005
882
  else closeOverflow();
1006
883
  });
@@ -1027,35 +904,105 @@ function injectToolbar(doc, options) {
1027
904
  }
1028
905
  });
1029
906
  overflowMenu.appendChild(
1030
- makeSelect(
907
+ helpers.makeSelect(
1031
908
  "Format",
1032
909
  "formatBlock",
1033
- FORMAT_OPTIONS,
910
+ window.RHE_FORMAT_OPTIONS || [],
1034
911
  format.formatBlock
1035
912
  )
1036
913
  );
1037
914
  overflowMenu.appendChild(
1038
- makeSelect("Font", "fontName", FONT_OPTIONS, format.fontName)
915
+ helpers.makeSelect(
916
+ "Font",
917
+ "fontName",
918
+ window.RHE_FONT_OPTIONS || [],
919
+ format.fontName
920
+ )
1039
921
  );
1040
922
  overflowMenu.appendChild(
1041
- makeSelect("Size", "fontSize", SIZE_OPTIONS, format.fontSize)
923
+ helpers.makeSelect(
924
+ "Size",
925
+ "fontSize",
926
+ window.RHE_SIZE_OPTIONS || [],
927
+ format.fontSize
928
+ )
1042
929
  );
1043
930
  overflowMenu.appendChild(
1044
- makeColorInput("Text color", "foreColor", format.foreColor)
931
+ helpers.makeColorInput("Text color", "foreColor", format.foreColor)
1045
932
  );
1046
933
  overflowMenu.appendChild(
1047
- makeColorInput(
934
+ helpers.makeColorInput(
1048
935
  "Highlight color",
1049
936
  "hiliteColor",
1050
937
  format.hiliteColor
1051
938
  )
1052
939
  );
1053
- overflowMenu.appendChild(makeButton(LABEL_LINK, "Insert link", "link"));
1054
- const overflowWrap = makeGroup();
940
+ overflowMenu.appendChild(helpers.makeButton("Link", "Insert link", "link"));
941
+ const overflowWrap = helpers.makeGroup();
1055
942
  overflowWrap.className = "toolbar-group toolbar-overflow-wrap";
1056
943
  overflowWrap.appendChild(overflowBtn);
1057
944
  overflowWrap.appendChild(overflowMenu);
1058
945
  toolbar.appendChild(overflowWrap);
946
+ }
947
+
948
+ // src/toolbar/buttons.ts
949
+ function makeButton(doc, options, label, title, command, value, isActive, disabled) {
950
+ const btn = doc.createElement("button");
951
+ btn.type = "button";
952
+ if (label && label.trim().startsWith("<")) {
953
+ btn.innerHTML = label;
954
+ } else {
955
+ btn.textContent = label;
956
+ }
957
+ btn.title = title;
958
+ btn.setAttribute("aria-label", title);
959
+ if (typeof isActive !== "undefined")
960
+ btn.setAttribute("aria-pressed", String(!!isActive));
961
+ btn.tabIndex = 0;
962
+ if (disabled) btn.disabled = true;
963
+ btn.onclick = () => options.onCommand(command, value);
964
+ btn.addEventListener("keydown", (ev) => {
965
+ if (ev.key === "Enter" || ev.key === " ") {
966
+ ev.preventDefault();
967
+ btn.click();
968
+ }
969
+ });
970
+ return btn;
971
+ }
972
+ function makeGroup(doc) {
973
+ const g = doc.createElement("div");
974
+ g.className = "toolbar-group";
975
+ return g;
976
+ }
977
+ function makeSep(doc) {
978
+ const s = doc.createElement("div");
979
+ s.className = "toolbar-sep";
980
+ return s;
981
+ }
982
+
983
+ // src/toolbar/selects.ts
984
+ function makeSelect(doc, options, title, command, optionsList, initialValue) {
985
+ const select = doc.createElement("select");
986
+ select.title = title;
987
+ select.setAttribute("aria-label", title);
988
+ select.appendChild(new Option(title, "", true, true));
989
+ for (const opt of optionsList) {
990
+ select.appendChild(new Option(opt.label, opt.value));
991
+ }
992
+ try {
993
+ if (initialValue) select.value = initialValue;
994
+ } catch (e) {
995
+ }
996
+ select.onchange = (e) => {
997
+ const val = e.target.value;
998
+ options.onCommand(command, val);
999
+ select.selectedIndex = 0;
1000
+ };
1001
+ return select;
1002
+ }
1003
+
1004
+ // src/toolbar/navigation.ts
1005
+ function setupNavigation(toolbar) {
1059
1006
  toolbar.addEventListener("keydown", (e) => {
1060
1007
  const focusable = Array.from(
1061
1008
  toolbar.querySelectorAll("button, select, input, [tabindex]")
@@ -1078,6 +1025,158 @@ function injectToolbar(doc, options) {
1078
1025
  focusable[focusable.length - 1].focus();
1079
1026
  }
1080
1027
  });
1028
+ }
1029
+
1030
+ // src/toolbar/render.ts
1031
+ function injectToolbar(doc, options) {
1032
+ const existing = doc.getElementById(TOOLBAR_ID);
1033
+ if (existing) existing.remove();
1034
+ const toolbar = doc.createElement("div");
1035
+ toolbar.id = TOOLBAR_ID;
1036
+ toolbar.setAttribute("role", "toolbar");
1037
+ toolbar.setAttribute("aria-label", "Rich text editor toolbar");
1038
+ const makeButton2 = (label, title, command, value, isActive, disabled) => makeButton(
1039
+ doc,
1040
+ { onCommand: options.onCommand },
1041
+ label,
1042
+ title,
1043
+ command,
1044
+ value,
1045
+ isActive,
1046
+ disabled
1047
+ );
1048
+ const makeSelect2 = (title, command, optionsList, initialValue) => makeSelect(
1049
+ doc,
1050
+ { onCommand: options.onCommand },
1051
+ title,
1052
+ command,
1053
+ optionsList,
1054
+ initialValue
1055
+ );
1056
+ const format = options.getFormatState();
1057
+ const makeGroup2 = () => makeGroup(doc);
1058
+ const makeSep2 = () => makeSep(doc);
1059
+ const undoBtn = makeButton2(
1060
+ LABEL_UNDO,
1061
+ "Undo",
1062
+ "undo",
1063
+ void 0,
1064
+ false,
1065
+ !options.canUndo()
1066
+ );
1067
+ undoBtn.onclick = () => options.onUndo();
1068
+ const redoBtn = makeButton2(
1069
+ LABEL_REDO,
1070
+ "Redo",
1071
+ "redo",
1072
+ void 0,
1073
+ false,
1074
+ !options.canRedo()
1075
+ );
1076
+ redoBtn.onclick = () => options.onRedo();
1077
+ const grp1 = makeGroup2();
1078
+ grp1.appendChild(undoBtn);
1079
+ grp1.appendChild(redoBtn);
1080
+ toolbar.appendChild(grp1);
1081
+ toolbar.appendChild(makeSep2());
1082
+ const grp2 = makeGroup2();
1083
+ grp2.className = "toolbar-group collapse-on-small";
1084
+ grp2.appendChild(
1085
+ makeSelect2(
1086
+ "Format",
1087
+ "formatBlock",
1088
+ FORMAT_OPTIONS,
1089
+ format.formatBlock
1090
+ )
1091
+ );
1092
+ grp2.appendChild(
1093
+ makeSelect2("Font", "fontName", FONT_OPTIONS, format.fontName)
1094
+ );
1095
+ grp2.appendChild(
1096
+ makeSelect2("Size", "fontSize", SIZE_OPTIONS, format.fontSize)
1097
+ );
1098
+ toolbar.appendChild(grp2);
1099
+ toolbar.appendChild(makeSep2());
1100
+ const grp3 = makeGroup2();
1101
+ grp3.appendChild(
1102
+ makeButton2(LABEL_BOLD, "Bold", "bold", void 0, format.bold)
1103
+ );
1104
+ grp3.appendChild(
1105
+ makeButton2(LABEL_ITALIC, "Italic", "italic", void 0, format.italic)
1106
+ );
1107
+ grp3.appendChild(
1108
+ makeButton2(
1109
+ LABEL_UNDERLINE,
1110
+ "Underline",
1111
+ "underline",
1112
+ void 0,
1113
+ format.underline
1114
+ )
1115
+ );
1116
+ grp3.appendChild(makeButton2(LABEL_STRIKETHROUGH, "Strikethrough", "strike"));
1117
+ grp3.appendChild(
1118
+ makeButton2(
1119
+ LABEL_UNORDERED_LIST,
1120
+ "Unordered list",
1121
+ "unorderedList",
1122
+ void 0,
1123
+ format.listType === "ul"
1124
+ )
1125
+ );
1126
+ grp3.appendChild(
1127
+ makeButton2(
1128
+ LABEL_ORDERED_LIST,
1129
+ "Ordered list",
1130
+ "orderedList",
1131
+ void 0,
1132
+ format.listType === "ol"
1133
+ )
1134
+ );
1135
+ toolbar.appendChild(grp3);
1136
+ toolbar.appendChild(makeSep2());
1137
+ const grp4 = makeGroup2();
1138
+ grp4.appendChild(makeButton2(LABEL_ALIGN_LEFT, "Align left", "align", "left"));
1139
+ grp4.appendChild(
1140
+ makeButton2(LABEL_ALIGN_CENTER, "Align center", "align", "center")
1141
+ );
1142
+ grp4.appendChild(
1143
+ makeButton2(LABEL_ALIGN_RIGHT, "Align right", "align", "right")
1144
+ );
1145
+ toolbar.appendChild(grp4);
1146
+ toolbar.appendChild(makeSep2());
1147
+ const grp5 = makeGroup2();
1148
+ grp5.className = "toolbar-group collapse-on-small";
1149
+ grp5.appendChild(
1150
+ makeColorInput(
1151
+ doc,
1152
+ options,
1153
+ "Text color",
1154
+ "foreColor",
1155
+ format.foreColor
1156
+ )
1157
+ );
1158
+ grp5.appendChild(
1159
+ makeColorInput(
1160
+ doc,
1161
+ options,
1162
+ "Highlight color",
1163
+ "hiliteColor",
1164
+ format.hiliteColor
1165
+ )
1166
+ );
1167
+ toolbar.appendChild(grp5);
1168
+ toolbar.appendChild(makeSep2());
1169
+ const grp6 = makeGroup2();
1170
+ grp6.className = "toolbar-group collapse-on-small";
1171
+ grp6.appendChild(makeButton2(LABEL_LINK, "Insert link", "link"));
1172
+ toolbar.appendChild(grp6);
1173
+ setupOverflow(doc, toolbar, options, format, {
1174
+ makeSelect: makeSelect2,
1175
+ makeColorInput: (title, command, initial) => makeColorInput(doc, options, title, command, initial),
1176
+ makeButton: makeButton2,
1177
+ makeGroup: makeGroup2
1178
+ });
1179
+ setupNavigation(toolbar);
1081
1180
  doc.body.insertBefore(toolbar, doc.body.firstChild);
1082
1181
  }
1083
1182
 
@@ -1120,7 +1219,8 @@ function computeFormatState(doc) {
1120
1219
  hiliteColor: null,
1121
1220
  fontName: null,
1122
1221
  fontSize: null,
1123
- formatBlock: null
1222
+ formatBlock: null,
1223
+ listType: null
1124
1224
  };
1125
1225
  const computed = (_a = doc.defaultView) == null ? void 0 : _a.getComputedStyle(el);
1126
1226
  const bold = !!(el.closest("strong, b") || computed && (computed.fontWeight === "700" || Number(computed.fontWeight) >= 700));
@@ -1156,6 +1256,18 @@ function computeFormatState(doc) {
1156
1256
  blockEl = blockEl.parentElement;
1157
1257
  }
1158
1258
  const formatBlock = blockEl ? blockEl.tagName.toLowerCase() : null;
1259
+ let listType = null;
1260
+ try {
1261
+ if (blockEl && blockEl.tagName === "LI") {
1262
+ const list = blockEl.closest("ul,ol");
1263
+ if (list) listType = list.tagName.toLowerCase();
1264
+ } else {
1265
+ const possible = el.closest("ul,ol");
1266
+ if (possible) listType = possible.tagName.toLowerCase();
1267
+ }
1268
+ } catch (e) {
1269
+ listType = null;
1270
+ }
1159
1271
  return {
1160
1272
  bold,
1161
1273
  italic,
@@ -1164,7 +1276,8 @@ function computeFormatState(doc) {
1164
1276
  hiliteColor,
1165
1277
  fontName,
1166
1278
  fontSize,
1167
- formatBlock
1279
+ formatBlock,
1280
+ listType
1168
1281
  };
1169
1282
  } catch (err) {
1170
1283
  return {
@@ -1175,7 +1288,8 @@ function computeFormatState(doc) {
1175
1288
  hiliteColor: null,
1176
1289
  fontName: null,
1177
1290
  fontSize: null,
1178
- formatBlock: null
1291
+ formatBlock: null,
1292
+ listType: null
1179
1293
  };
1180
1294
  }
1181
1295
  }
@@ -1571,6 +1685,9 @@ function applyStandaloneCommand(command, value) {
1571
1685
  } else {
1572
1686
  wrapSelectionWithElement(doc, tag);
1573
1687
  }
1688
+ } else if (command === "unorderedList" || command === "orderedList") {
1689
+ const tag = command === "unorderedList" ? "ul" : "ol";
1690
+ toggleList(doc, tag);
1574
1691
  }
1575
1692
  pushStandaloneSnapshot();
1576
1693
  } catch (error) {
@@ -1606,6 +1723,57 @@ function wrapSelectionWithElement(doc, tagName, style) {
1606
1723
  newRange.selectNodeContents(wrapper);
1607
1724
  sel.addRange(newRange);
1608
1725
  }
1726
+ function toggleList(doc, listTag) {
1727
+ var _a, _b;
1728
+ const sel = doc.getSelection();
1729
+ if (!sel || !sel.rangeCount) return;
1730
+ const node = sel.anchorNode || null;
1731
+ const block = findBlockAncestor(node);
1732
+ if (block && block.tagName === "LI" && block.parentElement) {
1733
+ const parentList = block.parentElement;
1734
+ const parentTag = parentList.tagName.toLowerCase();
1735
+ if (parentTag === listTag) {
1736
+ const frag = doc.createDocumentFragment();
1737
+ Array.from(parentList.children).forEach((li2) => {
1738
+ const p = doc.createElement("p");
1739
+ while (li2.firstChild) p.appendChild(li2.firstChild);
1740
+ frag.appendChild(p);
1741
+ });
1742
+ (_a = parentList.parentElement) == null ? void 0 : _a.replaceChild(frag, parentList);
1743
+ return;
1744
+ } else {
1745
+ const newList = doc.createElement(listTag);
1746
+ while (parentList.firstChild) newList.appendChild(parentList.firstChild);
1747
+ (_b = parentList.parentElement) == null ? void 0 : _b.replaceChild(newList, parentList);
1748
+ return;
1749
+ }
1750
+ }
1751
+ const range = sel.getRangeAt(0);
1752
+ if (range.collapsed) {
1753
+ const list2 = doc.createElement(listTag);
1754
+ const li2 = doc.createElement("li");
1755
+ const zw = doc.createTextNode("\u200B");
1756
+ li2.appendChild(zw);
1757
+ list2.appendChild(li2);
1758
+ range.insertNode(list2);
1759
+ const newRange2 = doc.createRange();
1760
+ newRange2.setStart(zw, 1);
1761
+ newRange2.collapse(true);
1762
+ sel.removeAllRanges();
1763
+ sel.addRange(newRange2);
1764
+ return;
1765
+ }
1766
+ const content = range.extractContents();
1767
+ const list = doc.createElement(listTag);
1768
+ const li = doc.createElement("li");
1769
+ li.appendChild(content);
1770
+ list.appendChild(li);
1771
+ range.insertNode(list);
1772
+ sel.removeAllRanges();
1773
+ const newRange = doc.createRange();
1774
+ newRange.selectNodeContents(li);
1775
+ sel.addRange(newRange);
1776
+ }
1609
1777
  function findBlockAncestor(node) {
1610
1778
  let n = node;
1611
1779
  const BLOCKS = [
@@ -1748,6 +1916,33 @@ function attachStandaloneHandlers(doc) {
1748
1916
  },
1749
1917
  true
1750
1918
  );
1919
+ doc.addEventListener(
1920
+ "keydown",
1921
+ (e) => {
1922
+ if (e.key !== "Enter") return;
1923
+ if (e.shiftKey) return;
1924
+ const sel = doc.getSelection();
1925
+ if (!sel || !sel.rangeCount) return;
1926
+ const node = sel.anchorNode;
1927
+ const el = node && node.nodeType === Node.ELEMENT_NODE ? node : node && node.parentElement || null;
1928
+ if (!el) return;
1929
+ const li = el.closest("li");
1930
+ if (!li || !li.parentElement) return;
1931
+ e.preventDefault();
1932
+ const list = li.parentElement;
1933
+ const newLi = doc.createElement("li");
1934
+ const zw = doc.createTextNode("\u200B");
1935
+ newLi.appendChild(zw);
1936
+ if (li.nextSibling) list.insertBefore(newLi, li.nextSibling);
1937
+ else list.appendChild(newLi);
1938
+ const range = doc.createRange();
1939
+ range.setStart(zw, 1);
1940
+ range.collapse(true);
1941
+ sel.removeAllRanges();
1942
+ sel.addRange(range);
1943
+ },
1944
+ true
1945
+ );
1751
1946
  }
1752
1947
 
1753
1948
  // src/core/editor.ts