@lukso/web-components 1.154.0 → 1.155.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 (191) hide show
  1. package/README.md +12 -0
  2. package/dist/components/index.cjs +14 -4
  3. package/dist/components/index.d.ts +2 -0
  4. package/dist/components/index.d.ts.map +1 -1
  5. package/dist/components/index.js +6 -4
  6. package/dist/components/lukso-button/index.cjs +3 -3
  7. package/dist/components/lukso-button/index.js +3 -3
  8. package/dist/components/lukso-card/index.cjs +7 -7
  9. package/dist/components/lukso-card/index.js +7 -7
  10. package/dist/components/lukso-checkbox/index.cjs +3 -3
  11. package/dist/components/lukso-checkbox/index.js +3 -3
  12. package/dist/components/lukso-collapse/index.cjs +5 -11
  13. package/dist/components/lukso-collapse/index.js +4 -10
  14. package/dist/components/lukso-color-picker/index.cjs +4 -4
  15. package/dist/components/lukso-color-picker/index.js +4 -4
  16. package/dist/components/lukso-dropdown/index.cjs +11 -7
  17. package/dist/components/lukso-dropdown/index.d.ts +1 -0
  18. package/dist/components/lukso-dropdown/index.d.ts.map +1 -1
  19. package/dist/components/lukso-dropdown/index.js +11 -7
  20. package/dist/components/lukso-dropdown-option/index.cjs +2 -2
  21. package/dist/components/lukso-dropdown-option/index.js +2 -2
  22. package/dist/components/lukso-footer/index.cjs +2 -2
  23. package/dist/components/lukso-footer/index.js +2 -2
  24. package/dist/components/lukso-icon/index.cjs +283 -18
  25. package/dist/components/lukso-icon/index.d.ts +21 -0
  26. package/dist/components/lukso-icon/index.d.ts.map +1 -1
  27. package/dist/components/lukso-icon/index.js +282 -18
  28. package/dist/components/lukso-icon/lukso-icon.stories.d.ts +5 -0
  29. package/dist/components/lukso-icon/lukso-icon.stories.d.ts.map +1 -1
  30. package/dist/components/lukso-icon/vuesax/bold/attach-square.svg +3 -0
  31. package/dist/components/lukso-icon/vuesax/bold/eye-slash.svg +7 -0
  32. package/dist/components/lukso-icon/vuesax/bold/eye.svg +4 -0
  33. package/dist/components/lukso-icon/vuesax/bold/link.svg +5 -0
  34. package/dist/components/lukso-icon/vuesax/bold/smallcaps.svg +3 -0
  35. package/dist/components/lukso-icon/vuesax/bold/task.svg +8 -0
  36. package/dist/components/lukso-icon/vuesax/bold/text-bold.svg +5 -0
  37. package/dist/components/lukso-icon/vuesax/bold/text-italic.svg +3 -0
  38. package/dist/components/lukso-icon/vuesax/bold/textalign-center.svg +6 -0
  39. package/dist/components/lukso-icon/vuesax/bold/textalign-justifycenter.svg +6 -0
  40. package/dist/components/lukso-icon/vuesax/bold/textalign-left.svg +6 -0
  41. package/dist/components/lukso-icon/vuesax/bold/textalign-right.svg +6 -0
  42. package/dist/components/lukso-icon/vuesax/broken/attach-square.svg +4 -0
  43. package/dist/components/lukso-icon/vuesax/broken/eye-slash.svg +9 -0
  44. package/dist/components/lukso-icon/vuesax/broken/eye.svg +4 -0
  45. package/dist/components/lukso-icon/vuesax/broken/link.svg +6 -0
  46. package/dist/components/lukso-icon/vuesax/broken/smallcaps.svg +9 -0
  47. package/dist/components/lukso-icon/vuesax/broken/task.svg +9 -0
  48. package/dist/components/lukso-icon/vuesax/broken/text-bold.svg +4 -0
  49. package/dist/components/lukso-icon/vuesax/broken/text-italic.svg +6 -0
  50. package/dist/components/lukso-icon/vuesax/broken/textalign-center.svg +7 -0
  51. package/dist/components/lukso-icon/vuesax/broken/textalign-justifycenter.svg +7 -0
  52. package/dist/components/lukso-icon/vuesax/broken/textalign-left.svg +7 -0
  53. package/dist/components/lukso-icon/vuesax/broken/textalign-right.svg +7 -0
  54. package/dist/components/lukso-icon/vuesax/bulk/attach-square.svg +4 -0
  55. package/dist/components/lukso-icon/vuesax/bulk/eye-slash.svg +7 -0
  56. package/dist/components/lukso-icon/vuesax/bulk/frame.svg +4 -0
  57. package/dist/components/lukso-icon/vuesax/bulk/link.svg +5 -0
  58. package/dist/components/lukso-icon/vuesax/bulk/smallcaps.svg +5 -0
  59. package/dist/components/lukso-icon/vuesax/bulk/task.svg +8 -0
  60. package/dist/components/lukso-icon/vuesax/bulk/text-bold.svg +4 -0
  61. package/dist/components/lukso-icon/vuesax/bulk/text-italic.svg +4 -0
  62. package/dist/components/lukso-icon/vuesax/bulk/textalign-center.svg +6 -0
  63. package/dist/components/lukso-icon/vuesax/bulk/textalign-justifycenter.svg +6 -0
  64. package/dist/components/lukso-icon/vuesax/bulk/textalign-left.svg +6 -0
  65. package/dist/components/lukso-icon/vuesax/bulk/textalign-right.svg +6 -0
  66. package/dist/components/lukso-icon/vuesax/linear/attach-square.svg +4 -0
  67. package/dist/components/lukso-icon/vuesax/linear/eye-slash.svg +8 -0
  68. package/dist/components/lukso-icon/vuesax/linear/eye.svg +4 -0
  69. package/dist/components/lukso-icon/vuesax/linear/link.svg +5 -0
  70. package/dist/components/lukso-icon/vuesax/linear/smallcaps.svg +8 -0
  71. package/dist/components/lukso-icon/vuesax/linear/task.svg +8 -0
  72. package/dist/components/lukso-icon/vuesax/linear/text-bold.svg +4 -0
  73. package/dist/components/lukso-icon/vuesax/linear/text-italic.svg +5 -0
  74. package/dist/components/lukso-icon/vuesax/linear/textalign-center.svg +6 -0
  75. package/dist/components/lukso-icon/vuesax/linear/textalign-justifycenter.svg +6 -0
  76. package/dist/components/lukso-icon/vuesax/linear/textalign-left.svg +6 -0
  77. package/dist/components/lukso-icon/vuesax/linear/textalign-right.svg +6 -0
  78. package/dist/components/lukso-icon/vuesax/outline/attach-square.svg +4 -0
  79. package/dist/components/lukso-icon/vuesax/outline/eye-slash.svg +8 -0
  80. package/dist/components/lukso-icon/vuesax/outline/eye.svg +4 -0
  81. package/dist/components/lukso-icon/vuesax/outline/link.svg +5 -0
  82. package/dist/components/lukso-icon/vuesax/outline/smallcaps.svg +8 -0
  83. package/dist/components/lukso-icon/vuesax/outline/task.svg +8 -0
  84. package/dist/components/lukso-icon/vuesax/outline/text-bold.svg +4 -0
  85. package/dist/components/lukso-icon/vuesax/outline/text-italic.svg +5 -0
  86. package/dist/components/lukso-icon/vuesax/outline/textalign-center.svg +6 -0
  87. package/dist/components/lukso-icon/vuesax/outline/textalign-justifycenter.svg +6 -0
  88. package/dist/components/lukso-icon/vuesax/outline/textalign-left.svg +6 -0
  89. package/dist/components/lukso-icon/vuesax/outline/textalign-right.svg +6 -0
  90. package/dist/components/lukso-icon/vuesax/twotone/attach-square.svg +4 -0
  91. package/dist/components/lukso-icon/vuesax/twotone/eye-slash.svg +8 -0
  92. package/dist/components/lukso-icon/vuesax/twotone/eye.svg +4 -0
  93. package/dist/components/lukso-icon/vuesax/twotone/link.svg +5 -0
  94. package/dist/components/lukso-icon/vuesax/twotone/smallcaps.svg +10 -0
  95. package/dist/components/lukso-icon/vuesax/twotone/task.svg +8 -0
  96. package/dist/components/lukso-icon/vuesax/twotone/text-bold.svg +4 -0
  97. package/dist/components/lukso-icon/vuesax/twotone/text-italic.svg +5 -0
  98. package/dist/components/lukso-icon/vuesax/twotone/textalign-center.svg +6 -0
  99. package/dist/components/lukso-icon/vuesax/twotone/textalign-justifycenter.svg +6 -0
  100. package/dist/components/lukso-icon/vuesax/twotone/textalign-left.svg +6 -0
  101. package/dist/components/lukso-icon/vuesax/twotone/textalign-right.svg +6 -0
  102. package/dist/components/lukso-image/index.cjs +4 -4
  103. package/dist/components/lukso-image/index.js +4 -4
  104. package/dist/components/lukso-input/index.cjs +3 -3
  105. package/dist/components/lukso-input/index.js +3 -3
  106. package/dist/components/lukso-markdown/index.cjs +168 -0
  107. package/dist/components/lukso-markdown/index.d.ts +22 -0
  108. package/dist/components/lukso-markdown/index.d.ts.map +1 -0
  109. package/dist/components/lukso-markdown/index.js +166 -0
  110. package/dist/components/lukso-markdown/lukso-markdown.stories.d.ts +13 -0
  111. package/dist/components/lukso-markdown/lukso-markdown.stories.d.ts.map +1 -0
  112. package/dist/components/lukso-markdown-editor/index.cjs +1996 -0
  113. package/dist/components/lukso-markdown-editor/index.d.ts +255 -0
  114. package/dist/components/lukso-markdown-editor/index.d.ts.map +1 -0
  115. package/dist/components/lukso-markdown-editor/index.js +1994 -0
  116. package/dist/components/lukso-markdown-editor/lukso-markdown-editor.stories.d.ts +27 -0
  117. package/dist/components/lukso-markdown-editor/lukso-markdown-editor.stories.d.ts.map +1 -0
  118. package/dist/components/lukso-modal/index.cjs +2 -2
  119. package/dist/components/lukso-modal/index.js +2 -2
  120. package/dist/components/lukso-navbar/index.cjs +3 -3
  121. package/dist/components/lukso-navbar/index.js +3 -3
  122. package/dist/components/lukso-pagination/index.cjs +3 -3
  123. package/dist/components/lukso-pagination/index.js +3 -3
  124. package/dist/components/lukso-profile/index.cjs +3 -3
  125. package/dist/components/lukso-profile/index.js +3 -3
  126. package/dist/components/lukso-progress/index.cjs +3 -3
  127. package/dist/components/lukso-progress/index.js +3 -3
  128. package/dist/components/lukso-radio/index.cjs +3 -3
  129. package/dist/components/lukso-radio/index.js +3 -3
  130. package/dist/components/lukso-radio-group/index.cjs +3 -3
  131. package/dist/components/lukso-radio-group/index.js +3 -3
  132. package/dist/components/lukso-sanitize/index.cjs +4 -10
  133. package/dist/components/lukso-sanitize/index.js +4 -10
  134. package/dist/components/lukso-search/index.cjs +6 -6
  135. package/dist/components/lukso-search/index.d.ts.map +1 -1
  136. package/dist/components/lukso-search/index.js +6 -6
  137. package/dist/components/lukso-select/index.cjs +5 -5
  138. package/dist/components/lukso-select/index.js +5 -5
  139. package/dist/components/lukso-share/index.cjs +2 -2
  140. package/dist/components/lukso-share/index.js +2 -2
  141. package/dist/components/lukso-switch/index.cjs +3 -3
  142. package/dist/components/lukso-switch/index.js +3 -3
  143. package/dist/components/lukso-tag/index.cjs +3 -3
  144. package/dist/components/lukso-tag/index.js +3 -3
  145. package/dist/components/lukso-terms/index.cjs +3 -3
  146. package/dist/components/lukso-terms/index.js +3 -3
  147. package/dist/components/lukso-textarea/index.cjs +3 -3
  148. package/dist/components/lukso-textarea/index.js +3 -3
  149. package/dist/components/lukso-tooltip/index.cjs +4 -4
  150. package/dist/components/lukso-tooltip/index.js +4 -4
  151. package/dist/components/lukso-username/index.cjs +5 -5
  152. package/dist/components/lukso-username/index.js +5 -5
  153. package/dist/components/lukso-wizard/index.cjs +2 -2
  154. package/dist/components/lukso-wizard/index.js +2 -2
  155. package/dist/docs/VuesaxPack.stories.d.ts +6 -0
  156. package/dist/docs/VuesaxPack.stories.d.ts.map +1 -0
  157. package/dist/{index-6Bz9XVC3.js → index-1cnTXnRX.js} +4 -4
  158. package/dist/{index-DUwutUFV.js → index-B5_11hN3.js} +5 -5
  159. package/dist/index-BWp0TAbf.js +41 -0
  160. package/dist/{index-Dg9hcDqR.cjs → index-CO57mpzm.cjs} +5 -5
  161. package/dist/{index-gJTlTDGh.js → index-CsepkLbs.js} +1 -1
  162. package/dist/{index-m3XGqZFz.cjs → index-D-a6OWyj.cjs} +1 -1
  163. package/dist/{index-U4Y7KwZv.cjs → index-D4HqZcbk.cjs} +2 -2
  164. package/dist/{index-fSZGRljb.cjs → index-D9mdD_OM.cjs} +4 -4
  165. package/dist/{index-CvWyP3CS.js → index-DFculCCQ.js} +2 -2
  166. package/dist/index-KrWvJ44l.cjs +50 -0
  167. package/dist/index.cjs +14 -4
  168. package/dist/index.js +6 -4
  169. package/dist/{isAddress-B3b91Jxf.cjs → isAddress-7c5mkwjj.cjs} +1 -1
  170. package/dist/{isAddress-Dq9UN07g.js → isAddress-DyEmEF7O.js} +1 -1
  171. package/dist/{property-BXmHod5d.cjs → property-CLMAG2Rj.cjs} +1 -1
  172. package/dist/{property-B4XYi6Sk.js → property-DkFGx5Fg.js} +1 -1
  173. package/dist/query-CHb9Ft_d.js +9 -0
  174. package/dist/query-EFiHeHdi.cjs +11 -0
  175. package/dist/shared/tailwind-element/index.cjs +1 -1
  176. package/dist/shared/tailwind-element/index.js +1 -1
  177. package/dist/{state-CkvZ4IP8.js → state-DPXXwNMf.js} +1 -1
  178. package/dist/{state-CW2YmM3f.cjs → state-DXBdLH-W.cjs} +1 -1
  179. package/dist/{style-map-c2S85yDu.cjs → style-map-BFG88xg5.cjs} +1 -1
  180. package/dist/{style-map-D1R4wi4h.js → style-map-CXXmrGLe.js} +1 -1
  181. package/dist/unsafe-html-C4RqiLkE.js +10 -0
  182. package/dist/unsafe-html-CxwvRvgp.cjs +12 -0
  183. package/dist/vite.full.config.d.ts.map +1 -1
  184. package/package.json +11 -1
  185. package/tailwind.config.cjs +1 -0
  186. package/tools/copy-assets.cjs +16 -16
  187. package/tools/copy-assets.js +2 -2
  188. package/tools/index.cjs +1 -1
  189. package/tools/index.js +1 -1
  190. package/dist/index-C9vH8YlV.js +0 -41
  191. package/dist/index-DkfODalz.cjs +0 -50
@@ -0,0 +1,1994 @@
1
+ import { T as TailwindStyledElement, E, x } from '../../index-BWp0TAbf.js';
2
+ import { n, t } from '../../property-DkFGx5Fg.js';
3
+ import { r } from '../../state-DPXXwNMf.js';
4
+ import { e } from '../../query-CHb9Ft_d.js';
5
+ import { c as ce } from '../../index-B9iart53.js';
6
+ import '../../tailwind-config.js';
7
+ import { c as cn } from '../../cn-Cu9aP49j.js';
8
+ import '../lukso-textarea/index.js';
9
+ import '../lukso-markdown/index.js';
10
+ import '../lukso-switch/index.js';
11
+ import '../lukso-button/index.js';
12
+ import '../lukso-icon/index.js';
13
+ import '../lukso-sanitize/index.js';
14
+ import '../lukso-dropdown/index.js';
15
+ import '../lukso-dropdown-option/index.js';
16
+ import '../lukso-tooltip/index.js';
17
+
18
+ const style = ":host {\n display: flex\n}";
19
+
20
+ var __defProp = Object.defineProperty;
21
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
22
+ var __decorateClass = (decorators, target, key, kind) => {
23
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
24
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
25
+ if (decorator = decorators[i])
26
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
27
+ if (kind && result) __defProp(target, key, result);
28
+ return result;
29
+ };
30
+ let LuksoMarkdownEditor = class extends TailwindStyledElement(style) {
31
+ constructor() {
32
+ super(...arguments);
33
+ this.value = "";
34
+ this.name = "";
35
+ this.label = "";
36
+ this.description = "";
37
+ this.error = "";
38
+ this.isFullWidth = false;
39
+ this.isReadonly = false;
40
+ this.isDisabled = false;
41
+ this.isNonResizable = false;
42
+ this.autofocus = false;
43
+ this.size = "large";
44
+ this.isPreview = false;
45
+ this.rows = 6;
46
+ this.placeholder = "";
47
+ this.savedSelectionForPreview = null;
48
+ this.isHeadingDropdownOpen = false;
49
+ this.isColorDropdownOpen = false;
50
+ this.isListDropdownOpen = false;
51
+ this.headingTriggerId = "heading-dropdown-trigger";
52
+ this.colorTriggerId = "color-dropdown-trigger";
53
+ this.listTriggerId = "list-dropdown-trigger";
54
+ this.currentSelection = { start: 0, end: 0 };
55
+ this.savedSelection = null;
56
+ this.activeFormats = {
57
+ bold: false,
58
+ italic: false,
59
+ link: false,
60
+ h1: false,
61
+ h2: false,
62
+ h3: false,
63
+ color: false,
64
+ activeColor: "",
65
+ unorderedList: false,
66
+ orderedList: false
67
+ };
68
+ // Undo/Redo state
69
+ this.undoStack = [];
70
+ this.redoStack = [];
71
+ this.isUndoRedoAction = false;
72
+ this.lastSavedValue = "";
73
+ this.undoTimeout = null;
74
+ this.MAX_UNDO_STACK_SIZE = 100;
75
+ this.UNDO_SAVE_DELAY = 500;
76
+ this.handleOutsideClick = (event) => {
77
+ const target = event.target;
78
+ const isInsideThisComponent = this.contains(target) || this.shadowRoot?.contains(target);
79
+ if (!isInsideThisComponent) {
80
+ if (this.isHeadingDropdownOpen) {
81
+ this.isHeadingDropdownOpen = false;
82
+ }
83
+ if (this.isColorDropdownOpen) {
84
+ this.isColorDropdownOpen = false;
85
+ }
86
+ if (this.isListDropdownOpen) {
87
+ this.isListDropdownOpen = false;
88
+ }
89
+ return;
90
+ }
91
+ const isInsideHeadingDropdown = this.shadowRoot?.getElementById("headingDropdown")?.contains(target);
92
+ const isInsideColorDropdown = this.shadowRoot?.getElementById("colorDropdown")?.contains(target);
93
+ const isInsideListDropdown = this.shadowRoot?.getElementById("listDropdown")?.contains(target);
94
+ const isHeadingTrigger = this.shadowRoot?.getElementById(this.headingTriggerId)?.contains(target);
95
+ const isColorTrigger = this.shadowRoot?.getElementById(this.colorTriggerId)?.contains(target);
96
+ const isListTrigger = this.shadowRoot?.getElementById(this.listTriggerId)?.contains(target);
97
+ if (!isInsideHeadingDropdown && !isHeadingTrigger && this.isHeadingDropdownOpen) {
98
+ this.isHeadingDropdownOpen = false;
99
+ }
100
+ if (!isInsideColorDropdown && !isColorTrigger && this.isColorDropdownOpen) {
101
+ this.isColorDropdownOpen = false;
102
+ }
103
+ if (!isInsideListDropdown && !isListTrigger && this.isListDropdownOpen) {
104
+ this.isListDropdownOpen = false;
105
+ }
106
+ };
107
+ this.styles = ce({
108
+ slots: {
109
+ wrapper: "w-[inherit] grid gap-3",
110
+ header: "flex items-center justify-between gap-2 border border-neutral-90 rounded-12 px-3 py-2",
111
+ toolbar: "flex flex-wrap items-center gap-1",
112
+ area: "",
113
+ editor: "",
114
+ preview: "p-3",
115
+ colorMenu: "relative",
116
+ headingMenu: "relative",
117
+ listMenu: "relative",
118
+ label: "heading-inter-14-bold text-neutral-20",
119
+ description: "paragraph-inter-12-regular text-neutral-20",
120
+ divider: "w-[1px] h-4 bg-neutral-90"
121
+ },
122
+ variants: {
123
+ isFullWidth: {
124
+ true: { wrapper: "w-full" },
125
+ false: { wrapper: "w-[350px]" }
126
+ }
127
+ },
128
+ compoundVariants: [{ isFullWidth: false, class: { wrapper: "w-[500px]" } }]
129
+ });
130
+ this.toolbarButton = ce({
131
+ base: "hover:bg-neutral-95 transition border-0 !shadow-none",
132
+ variants: {
133
+ active: { true: "bg-neutral-95" },
134
+ disabled: { true: "opacity-50 cursor-not-allowed" }
135
+ }
136
+ });
137
+ this.defaultColor = "#374151";
138
+ // prose gray
139
+ /**
140
+ * Hex color palette for the color picker.
141
+ * A curated collection of colors organized by hue families,
142
+ * ranging from light pastels to bold colors.
143
+ */
144
+ this.colorSamples = [
145
+ "#FFE5E5",
146
+ // very light red
147
+ "#FFB3B3",
148
+ "#FF8080",
149
+ "#FF6666",
150
+ "#FF4D4D",
151
+ "#E63946",
152
+ // strong pastel red
153
+ "#FFEBD5",
154
+ // very light orange
155
+ "#FFD1A3",
156
+ "#FFB870",
157
+ "#FFA54D",
158
+ "#FF944D",
159
+ "#FF7F11",
160
+ // bold pastel orange
161
+ "#FFFBD5",
162
+ // very light yellow
163
+ "#FFF3A3",
164
+ "#FFE870",
165
+ "#FFDD4D",
166
+ "#FFD633",
167
+ "#F4C430",
168
+ // deep pastel yellow
169
+ "#E6F9EC",
170
+ // very light green
171
+ "#B3E6C1",
172
+ "#80D499",
173
+ "#66CC80",
174
+ "#4DB870",
175
+ "#2D9C5B",
176
+ // bold pastel green
177
+ "#E6F3FF",
178
+ // very light blue
179
+ "#B3DAFF",
180
+ "#80C2FF",
181
+ "#66B2FF",
182
+ "#4DA6FF",
183
+ "#3A86FF",
184
+ // bold pastel blue
185
+ "#F3E6FF",
186
+ // very light purple
187
+ "#D9B3FF",
188
+ "#BF80FF",
189
+ "#A64DFF",
190
+ "#9333FF",
191
+ "#7B2CBF",
192
+ // bold pastel purple
193
+ "#FFE6F3",
194
+ // very light pink
195
+ "#FFB3D9",
196
+ "#FF80BF",
197
+ "#FF66B2",
198
+ "#FF4DA6",
199
+ "#E75480",
200
+ // bold pastel pink
201
+ "#F5E6DA",
202
+ // very light brown
203
+ "#E6CBB3",
204
+ "#D9B38C",
205
+ "#CC9966",
206
+ "#B3773A",
207
+ "#8B5E3C",
208
+ // bold pastel brown
209
+ "#FFFFFF",
210
+ // white
211
+ "#F5F5F5",
212
+ // very light gray
213
+ "#E0E0E0",
214
+ // light gray
215
+ "#BDBDBD",
216
+ // medium gray
217
+ "#757575",
218
+ // dark gray
219
+ "#000000",
220
+ // black
221
+ this.defaultColor
222
+ ];
223
+ /**
224
+ * Handle input event from the internal textarea component.
225
+ * @param event
226
+ */
227
+ this.handleTextareaInput = (event) => {
228
+ const newValue = event.detail?.value || "";
229
+ if (!this.isUndoRedoAction) {
230
+ this.scheduleUndoStateSave();
231
+ }
232
+ this.value = newValue;
233
+ this.updateActiveFormats();
234
+ };
235
+ /**
236
+ * Textarea keyup handler.
237
+ * Update active formats when user navigates with keyboard.
238
+ */
239
+ this.handleTextareaKeyUp = () => {
240
+ requestAnimationFrame(() => this.updateActiveFormats());
241
+ };
242
+ /**
243
+ * Textarea click handler.
244
+ * Update active formats when user clicks to move cursor.
245
+ */
246
+ this.handleTextareaClick = () => {
247
+ requestAnimationFrame(() => this.updateActiveFormats());
248
+ };
249
+ /**
250
+ * Handle keydown events for undo/redo shortcuts and list continuation.
251
+ */
252
+ this.handleKeyDown = (event) => {
253
+ const isUndo = (event.metaKey || event.ctrlKey) && event.key === "z" && !event.shiftKey;
254
+ const isRedo = (event.metaKey || event.ctrlKey) && (event.key === "y" || event.key === "z" && event.shiftKey);
255
+ if (isUndo) {
256
+ event.preventDefault();
257
+ this.undo();
258
+ } else if (isRedo) {
259
+ event.preventDefault();
260
+ this.redo();
261
+ } else if (event.key === "Enter" && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
262
+ if (this.handleListContinuation()) {
263
+ event.preventDefault();
264
+ }
265
+ } else if (event.key === "Backspace" && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
266
+ if (this.handleListBackspace()) {
267
+ event.preventDefault();
268
+ }
269
+ } else if (event.key === "Tab" && !event.metaKey && !event.ctrlKey) {
270
+ if (event.shiftKey) {
271
+ if (this.handleListOutdent()) {
272
+ event.preventDefault();
273
+ }
274
+ } else {
275
+ if (this.handleListIndent()) {
276
+ event.preventDefault();
277
+ }
278
+ }
279
+ }
280
+ };
281
+ }
282
+ dispatchChange(event) {
283
+ this.updateComplete.then(() => {
284
+ const changeEvent = new CustomEvent("on-change", {
285
+ detail: { value: this.value, event },
286
+ bubbles: false,
287
+ composed: true
288
+ });
289
+ this.dispatchEvent(changeEvent);
290
+ });
291
+ }
292
+ /**
293
+ * Utility to perform an operation with the current textarea selection.
294
+ *
295
+ * @param callback -
296
+ */
297
+ withSelection(callback) {
298
+ const textarea = this.textareaEl?.shadowRoot?.querySelector(
299
+ "textarea"
300
+ );
301
+ if (!textarea) return;
302
+ const start = textarea.selectionStart ?? 0;
303
+ const end = textarea.selectionEnd ?? 0;
304
+ const value = this.value;
305
+ callback(textarea, start, end, value);
306
+ textarea.focus();
307
+ }
308
+ /**
309
+ * Expand empty selection to current word boundaries
310
+ *
311
+ * @param start - selection start
312
+ * @param end - selection end
313
+ * @param value - full textarea value
314
+ */
315
+ expandSelectionToWord(start, end, value) {
316
+ if (start !== end) return { start, end };
317
+ const charBefore = start > 0 ? value[start - 1] : "";
318
+ const charAfter = start < value.length ? value[start] : "";
319
+ if (charBefore === "(" && charAfter === ")" || charBefore === "[" && charAfter === "]") {
320
+ return { start, end };
321
+ }
322
+ let _start = start;
323
+ let _end = end;
324
+ const isWord = (ch) => /[\p{L}\p{N}_-]/u.test(ch);
325
+ while (_start > 0 && isWord(value[_start - 1])) _start--;
326
+ while (_end < value.length && isWord(value[_end])) _end++;
327
+ return { start: _start, end: _end };
328
+ }
329
+ /**
330
+ * Apply or toggle heading formatting for the current line(s).
331
+ *
332
+ * @param level - 0 to remove heading, 1-3 for heading levels
333
+ */
334
+ applyHeading(level) {
335
+ if (this.isReadonly || this.isDisabled) return;
336
+ this.saveUndoStateBeforeChange();
337
+ const desiredPrefix = level > 0 ? `${"#".repeat(level)} ` : "";
338
+ this.withSelection((textarea, start, end, value) => {
339
+ const lineStart = value.lastIndexOf("\n", start - 1) + 1;
340
+ let lineEnd = value.indexOf("\n", end);
341
+ if (lineEnd === -1) lineEnd = value.length;
342
+ const before = value.slice(0, lineStart);
343
+ const selected = value.slice(lineStart, lineEnd);
344
+ const after = value.slice(lineEnd);
345
+ const lines = selected.split("\n");
346
+ const headingRegex = /^(#{1,6})\s+/;
347
+ const allAlreadyLevel = lines.every((l) => l.startsWith(desiredPrefix));
348
+ let transformed;
349
+ if (level === 0) {
350
+ transformed = lines.map((l) => l.replace(headingRegex, "")).join("\n");
351
+ } else {
352
+ transformed = lines.map((l) => {
353
+ const withoutAny = l.replace(headingRegex, "");
354
+ if (allAlreadyLevel) {
355
+ return withoutAny;
356
+ }
357
+ return desiredPrefix + withoutAny;
358
+ }).join("\n");
359
+ }
360
+ this.value = before + transformed + after;
361
+ textarea.value = before + transformed + after;
362
+ let cursorPosition = before.length;
363
+ if (level > 0 && !allAlreadyLevel) {
364
+ const firstLine = lines[0] || "";
365
+ const contentAfterHeading = firstLine.replace(headingRegex, "");
366
+ if (contentAfterHeading.trim()) {
367
+ const firstLineEnd = transformed.indexOf("\n");
368
+ cursorPosition = before.length + (firstLineEnd === -1 ? transformed.length : firstLineEnd);
369
+ } else {
370
+ cursorPosition = before.length + desiredPrefix.length;
371
+ }
372
+ } else if (level === 0 || allAlreadyLevel) {
373
+ const firstLineEnd = transformed.indexOf("\n");
374
+ cursorPosition = before.length + (firstLineEnd === -1 ? transformed.length : firstLineEnd);
375
+ }
376
+ requestAnimationFrame(() => {
377
+ textarea.setSelectionRange(cursorPosition, cursorPosition);
378
+ this.updateActiveFormats();
379
+ });
380
+ this.dispatchChange();
381
+ });
382
+ }
383
+ /**
384
+ * Apply or toggle list formatting for the current line(s).
385
+ *
386
+ * @param listType - 'none' to remove list, 'unordered' for bullet list, 'ordered' for numbered list
387
+ */
388
+ applyList(listType) {
389
+ if (this.isReadonly || this.isDisabled) return;
390
+ this.saveUndoStateBeforeChange();
391
+ this.withSelection((textarea, start, end, value) => {
392
+ const lineStart = value.lastIndexOf("\n", start - 1) + 1;
393
+ let lineEnd = value.indexOf("\n", end);
394
+ if (lineEnd === -1) lineEnd = value.length;
395
+ const before = value.slice(0, lineStart);
396
+ const selected = value.slice(lineStart, lineEnd);
397
+ const after = value.slice(lineEnd);
398
+ const lines = selected.split("\n");
399
+ const unorderedRegex = /^(\s*)[-*+]\s+/;
400
+ const orderedRegex = /^(\s*)\d+\.\s+/;
401
+ let transformed;
402
+ if (listType === "none") {
403
+ transformed = lines.map((l) => {
404
+ if (unorderedRegex.test(l)) {
405
+ return l.replace(unorderedRegex, "$1");
406
+ }
407
+ if (orderedRegex.test(l)) {
408
+ return l.replace(orderedRegex, "$1");
409
+ }
410
+ return l;
411
+ }).join("\n");
412
+ } else if (listType === "unordered") {
413
+ const allAlreadyUnordered = lines.every(
414
+ (l) => l.trim() === "" || unorderedRegex.test(l)
415
+ );
416
+ if (allAlreadyUnordered && lines.some((l) => unorderedRegex.test(l))) {
417
+ transformed = lines.map((l) => {
418
+ if (unorderedRegex.test(l)) {
419
+ return l.replace(unorderedRegex, "$1");
420
+ }
421
+ return l;
422
+ }).join("\n");
423
+ } else {
424
+ transformed = lines.map((l) => {
425
+ if (l.trim() === "") return l;
426
+ let cleaned = l;
427
+ if (unorderedRegex.test(l)) {
428
+ cleaned = l.replace(unorderedRegex, "$1");
429
+ } else if (orderedRegex.test(l)) {
430
+ cleaned = l.replace(orderedRegex, "$1");
431
+ }
432
+ const indentMatch = cleaned.match(/^(\s*)/);
433
+ const indent = indentMatch ? indentMatch[1] : "";
434
+ const content = cleaned.replace(/^\s*/, "");
435
+ return `${indent}- ${content}`;
436
+ }).join("\n");
437
+ }
438
+ } else if (listType === "ordered") {
439
+ const allAlreadyOrdered = lines.every(
440
+ (l) => l.trim() === "" || orderedRegex.test(l)
441
+ );
442
+ if (allAlreadyOrdered && lines.some((l) => orderedRegex.test(l))) {
443
+ transformed = lines.map((l) => {
444
+ if (orderedRegex.test(l)) {
445
+ return l.replace(orderedRegex, "$1");
446
+ }
447
+ return l;
448
+ }).join("\n");
449
+ } else {
450
+ let counter = 1;
451
+ const beforeLines = before.split("\n");
452
+ for (let i = beforeLines.length - 1; i >= 0; i--) {
453
+ const line = beforeLines[i];
454
+ const orderedMatch = line.match(/^(\s*)(\d+)\.\s*(.*)$/);
455
+ if (orderedMatch) {
456
+ counter = parseInt(orderedMatch[2], 10) + 1;
457
+ break;
458
+ } else if (line.trim() !== "" && !line.match(/^\s*[-*+]\s+/)) {
459
+ break;
460
+ }
461
+ }
462
+ transformed = lines.map((l) => {
463
+ if (l.trim() === "") return l;
464
+ let cleaned = l;
465
+ if (unorderedRegex.test(l)) {
466
+ cleaned = l.replace(unorderedRegex, "$1");
467
+ } else if (orderedRegex.test(l)) {
468
+ cleaned = l.replace(orderedRegex, "$1");
469
+ }
470
+ const indentMatch = cleaned.match(/^(\s*)/);
471
+ const indent = indentMatch ? indentMatch[1] : "";
472
+ const content = cleaned.replace(/^\s*/, "");
473
+ return `${indent}${counter++}. ${content}`;
474
+ }).join("\n");
475
+ }
476
+ }
477
+ let finalValue = before + transformed + after;
478
+ if (listType === "ordered") {
479
+ const allAlreadyOrdered = lines.every(
480
+ (l) => l.trim() === "" || orderedRegex.test(l)
481
+ );
482
+ if (!allAlreadyOrdered || !lines.some((l) => orderedRegex.test(l))) {
483
+ const transformedLines = transformed.split("\n");
484
+ const lastTransformedLine = transformedLines[transformedLines.length - 1];
485
+ const indentMatch = lastTransformedLine.match(/^(\s*)/);
486
+ const indent = indentMatch ? indentMatch[1] : "";
487
+ const endPosition = before.length + transformed.length;
488
+ finalValue = this.renumberOrderedListItems(
489
+ finalValue,
490
+ endPosition,
491
+ indent
492
+ );
493
+ }
494
+ }
495
+ if (listType === "none") {
496
+ const removedOrderedItems = lines.some((l) => orderedRegex.test(l));
497
+ if (removedOrderedItems) {
498
+ finalValue = this.renumberSubsequentOrderedLists(
499
+ finalValue,
500
+ before.length + transformed.length
501
+ );
502
+ }
503
+ }
504
+ this.value = finalValue;
505
+ textarea.value = finalValue;
506
+ const firstLineEnd = transformed.indexOf("\n");
507
+ const cursorPosition = before.length + (firstLineEnd === -1 ? transformed.length : firstLineEnd);
508
+ requestAnimationFrame(() => {
509
+ textarea.setSelectionRange(cursorPosition, cursorPosition);
510
+ this.updateActiveFormats();
511
+ });
512
+ this.dispatchChange();
513
+ });
514
+ }
515
+ /**
516
+ * Get the current active list type based on activeFormats.
517
+ */
518
+ getActiveListType() {
519
+ if (this.activeFormats.unorderedList) return "unordered";
520
+ if (this.activeFormats.orderedList) return "ordered";
521
+ return "none";
522
+ }
523
+ /**
524
+ * Toggle inline formatting by wrapping/unwrapping selection or current word.
525
+ *
526
+ * @param wrapper - the markdown wrapper to toggle, e.g. '**' for bold, '*' for italic
527
+ */
528
+ toggleWrap(wrapper) {
529
+ if (this.isReadonly || this.isDisabled) return;
530
+ this.saveUndoStateBeforeChange();
531
+ this.withSelection((textarea, start, end, value) => {
532
+ if (this.isSelectionInLinkUrl(start, end, value)) {
533
+ return;
534
+ }
535
+ const { start: s, end: e } = this.expandSelectionToWord(start, end, value);
536
+ let before = value.slice(0, s);
537
+ let selected = value.slice(s, e);
538
+ let after = value.slice(e);
539
+ const hasOuterWrap = before.endsWith(wrapper) && after.startsWith(wrapper);
540
+ if (hasOuterWrap) {
541
+ before = before.slice(0, before.length - wrapper.length);
542
+ after = after.slice(wrapper.length);
543
+ this.value = before + selected + after;
544
+ const selStart2 = before.length;
545
+ const selEnd2 = selStart2 + selected.length;
546
+ requestAnimationFrame(() => {
547
+ textarea.setSelectionRange(selStart2, selEnd2);
548
+ this.updateActiveFormats();
549
+ });
550
+ this.dispatchChange();
551
+ return;
552
+ }
553
+ const innerWrapped = selected.startsWith(wrapper) && selected.endsWith(wrapper);
554
+ if (innerWrapped) {
555
+ selected = selected.slice(
556
+ wrapper.length,
557
+ selected.length - wrapper.length
558
+ );
559
+ this.value = before + selected + after;
560
+ const selStart2 = before.length;
561
+ const selEnd2 = selStart2 + selected.length;
562
+ requestAnimationFrame(() => {
563
+ textarea.setSelectionRange(selStart2, selEnd2);
564
+ this.updateActiveFormats();
565
+ });
566
+ this.dispatchChange();
567
+ return;
568
+ }
569
+ const wrapped = `${wrapper}${selected || ""}${wrapper}`;
570
+ this.value = before + wrapped + after;
571
+ const selStart = before.length + wrapper.length;
572
+ const selEnd = selStart + (selected ? selected.length : 0);
573
+ requestAnimationFrame(() => {
574
+ textarea.setSelectionRange(selStart, selEnd);
575
+ this.updateActiveFormats();
576
+ });
577
+ this.dispatchChange();
578
+ });
579
+ }
580
+ /**
581
+ * Toggle preview mode on or off.
582
+ */
583
+ togglePreview() {
584
+ if (this.isPreview) {
585
+ this.exitPreview();
586
+ } else {
587
+ this.enterPreview();
588
+ }
589
+ this.isPreview = !this.isPreview;
590
+ }
591
+ /**
592
+ * Enter preview mode - save current state and remove keyboard listeners from textarea
593
+ */
594
+ enterPreview() {
595
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
596
+ if (textarea) {
597
+ this.savedSelectionForPreview = {
598
+ start: textarea.selectionStart ?? 0,
599
+ end: textarea.selectionEnd ?? 0
600
+ };
601
+ this.removeKeyboardListeners();
602
+ }
603
+ }
604
+ /**
605
+ * Exit preview mode - restore state and reattach keyboard listeners
606
+ */
607
+ exitPreview() {
608
+ requestAnimationFrame(() => {
609
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
610
+ if (textarea && this.savedSelectionForPreview) {
611
+ textarea.focus();
612
+ textarea.setSelectionRange(
613
+ this.savedSelectionForPreview.start,
614
+ this.savedSelectionForPreview.end
615
+ );
616
+ this.currentSelection = this.savedSelectionForPreview;
617
+ this.savedSelectionForPreview = null;
618
+ this.updateActiveFormats();
619
+ }
620
+ this.addKeyboardListeners();
621
+ });
622
+ }
623
+ /**
624
+ * Insert or edit a markdown link [text](url).
625
+ */
626
+ insertLink() {
627
+ if (this.isReadonly || this.isDisabled) return;
628
+ this.saveUndoStateBeforeChange();
629
+ const placeholderText = "link text";
630
+ const placeholderUrl = "";
631
+ this.withSelection((textarea, start, end, value) => {
632
+ if (this.isSelectionInLinkUrl(start, end, value) || this.isSelectionInLinkText(start, end, value)) {
633
+ const leftBracket2 = value.lastIndexOf("[", start);
634
+ const rightParen2 = value.indexOf(")", Math.max(start, end));
635
+ const rightBracket2 = value.indexOf("]", leftBracket2);
636
+ const openParen2 = value.indexOf("(", rightBracket2);
637
+ if (leftBracket2 !== -1 && rightBracket2 !== -1 && openParen2 !== -1 && rightParen2 !== -1) {
638
+ const candidate = value.slice(leftBracket2, rightParen2 + 1);
639
+ const linkRegex2 = /^\[([^\]]+)\]\(([^)]*)\)$/;
640
+ const match = candidate.match(linkRegex2);
641
+ if (match) {
642
+ const textOnly = match[1];
643
+ this.value = value.slice(0, leftBracket2) + textOnly + value.slice(rightParen2 + 1);
644
+ const newCursor = leftBracket2 + textOnly.length;
645
+ requestAnimationFrame(() => {
646
+ textarea.setSelectionRange(newCursor, newCursor);
647
+ this.updateActiveFormats();
648
+ });
649
+ this.dispatchChange();
650
+ return;
651
+ }
652
+ }
653
+ return;
654
+ }
655
+ const { start: s, end: e } = this.expandSelectionToWord(start, end, value);
656
+ const before = value.slice(0, s);
657
+ const selected = value.slice(s, e);
658
+ const after = value.slice(e);
659
+ const linkRegex = /^\[([^\]]+)\]\(([^)]+)\)$/;
660
+ if (linkRegex.test(selected)) {
661
+ const match = selected.match(linkRegex);
662
+ const textOnly = match?.[1] ?? selected;
663
+ this.value = before + textOnly + after;
664
+ const newStart = before.length;
665
+ const newEnd = newStart + textOnly.length;
666
+ requestAnimationFrame(() => {
667
+ textarea.setSelectionRange(newStart, newEnd);
668
+ this.updateActiveFormats();
669
+ });
670
+ this.dispatchChange();
671
+ return;
672
+ }
673
+ const leftBracket = value.lastIndexOf("[", s);
674
+ const rightParen = value.indexOf(")", e);
675
+ const rightBracket = value.indexOf("]", e);
676
+ const openParen = value.indexOf("(", e);
677
+ if (leftBracket !== -1 && rightBracket !== -1 && openParen !== -1 && rightParen !== -1 && leftBracket < rightBracket && rightBracket < openParen && openParen < rightParen) {
678
+ const candidate = value.slice(leftBracket, rightParen + 1);
679
+ if (linkRegex.test(candidate)) {
680
+ const match = candidate.match(linkRegex);
681
+ const textOnly = match?.[1] ?? candidate;
682
+ this.value = value.slice(0, leftBracket) + textOnly + value.slice(rightParen + 1);
683
+ const newCursor = leftBracket + textOnly.length;
684
+ requestAnimationFrame(() => {
685
+ textarea.setSelectionRange(newCursor, newCursor);
686
+ this.updateActiveFormats();
687
+ });
688
+ this.dispatchChange();
689
+ return;
690
+ }
691
+ }
692
+ const text = selected || placeholderText;
693
+ const md = `[${text}](${placeholderUrl})`;
694
+ this.value = before + md + after;
695
+ const cursorPosition = before.length + 1 + text.length + 2;
696
+ requestAnimationFrame(() => {
697
+ textarea.focus();
698
+ textarea.setSelectionRange(cursorPosition, cursorPosition);
699
+ this.updateActiveFormats();
700
+ });
701
+ this.dispatchChange();
702
+ });
703
+ }
704
+ /**
705
+ * Update active formatting states based on current selection or cursor position.
706
+ */
707
+ updateActiveFormats() {
708
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
709
+ if (!textarea || !this.value) return;
710
+ const start = textarea.selectionStart ?? 0;
711
+ const end = textarea.selectionEnd ?? 0;
712
+ this.currentSelection = { start, end };
713
+ const { start: s, end: e } = this.expandSelectionToWord(
714
+ start,
715
+ end,
716
+ this.value
717
+ );
718
+ const selectedText = this.value.slice(s, e);
719
+ const beforeSelection = this.value.slice(0, s);
720
+ const afterSelection = this.value.slice(e);
721
+ const hasBoldWrap = beforeSelection.endsWith("**") && afterSelection.startsWith("**") || selectedText.startsWith("**") && selectedText.endsWith("**");
722
+ const hasItalicWrap = beforeSelection.endsWith("*") && afterSelection.startsWith("*") && !beforeSelection.endsWith("**") || selectedText.startsWith("*") && selectedText.endsWith("*") && !selectedText.startsWith("**");
723
+ const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/;
724
+ const hasLink = linkRegex.test(selectedText) || this.isWithinLink(start, this.value);
725
+ const lineStart = this.value.lastIndexOf("\n", start - 1) + 1;
726
+ const lineEnd = this.value.indexOf("\n", start);
727
+ const currentLine = this.value.slice(
728
+ lineStart,
729
+ lineEnd === -1 ? this.value.length : lineEnd
730
+ );
731
+ const headingMatch = currentLine.match(/^(#{1,6})\s/);
732
+ const headingLevel = headingMatch ? headingMatch[1].length : 0;
733
+ const unorderedListMatch = currentLine.match(/^\s*[-*+]\s/);
734
+ const orderedListMatch = currentLine.match(/^\s*\d+\.\s/);
735
+ const hasUnorderedList = !!unorderedListMatch;
736
+ const hasOrderedList = !!orderedListMatch;
737
+ const colorRegex = /<span style="color: ([^"]+)">/;
738
+ const hasColorWrap = !!(beforeSelection.match(colorRegex) && afterSelection.includes("</span>") || selectedText.match(/^<span style="color: ([^"]+)">(.*)<\/span>$/s));
739
+ let activeColor = this.defaultColor;
740
+ if (hasColorWrap) {
741
+ const beforeColorMatch = beforeSelection.match(
742
+ /<span style="color: ([^"]+)">([^<]*)$/
743
+ );
744
+ const selectedColorMatch = selectedText.match(
745
+ /^<span style="color: ([^"]+)">/
746
+ );
747
+ activeColor = beforeColorMatch?.[1] || selectedColorMatch?.[1] || "";
748
+ }
749
+ this.activeFormats = {
750
+ bold: hasBoldWrap,
751
+ italic: hasItalicWrap,
752
+ link: hasLink,
753
+ h1: headingLevel === 1,
754
+ h2: headingLevel === 2,
755
+ h3: headingLevel === 3,
756
+ color: hasColorWrap,
757
+ activeColor,
758
+ unorderedList: hasUnorderedList,
759
+ orderedList: hasOrderedList
760
+ };
761
+ }
762
+ /**
763
+ * Check if a given position is within a markdown link [text](url).
764
+ * @param position - cursor position
765
+ * @param value - full textarea value
766
+ */
767
+ isWithinLink(position, value) {
768
+ const leftBracket = value.lastIndexOf("[", position);
769
+ const rightParen = value.indexOf(")", position);
770
+ if (leftBracket === -1 || rightParen === -1) return false;
771
+ const candidate = value.slice(leftBracket, rightParen + 1);
772
+ return /^\[([^\]]+)\]\(([^)]+)\)$/.test(candidate);
773
+ }
774
+ /**
775
+ * Check if selection is within the text part [] of a markdown link
776
+ * @param start - selection start position
777
+ * @param end - selection end position
778
+ * @param value - full textarea value
779
+ */
780
+ isSelectionInLinkText(start, end, value) {
781
+ const leftBracket = value.lastIndexOf("[", start);
782
+ const rightParen = value.indexOf(")", Math.max(start, end));
783
+ if (leftBracket === -1 || rightParen === -1) return false;
784
+ const rightBracket = value.indexOf("]", leftBracket);
785
+ const openParen = value.indexOf("(", rightBracket);
786
+ if (rightBracket === -1 || openParen === -1) return false;
787
+ if (!(leftBracket < rightBracket && rightBracket < openParen && openParen < rightParen))
788
+ return false;
789
+ const candidate = value.slice(leftBracket, rightParen + 1);
790
+ if (!/^\[([^\]]+)\]\(([^)]*)\)$/.test(candidate)) return false;
791
+ return start >= leftBracket + 1 && end <= rightBracket;
792
+ }
793
+ /**
794
+ * Check if selection is within the URL part () of a markdown link
795
+ * @param start - selection start position
796
+ * @param end - selection end position
797
+ * @param value - full textarea value
798
+ */
799
+ isSelectionInLinkUrl(start, end, value) {
800
+ const leftBracket = value.lastIndexOf("[", start);
801
+ const rightParen = value.indexOf(")", Math.max(start, end));
802
+ if (leftBracket === -1 || rightParen === -1) return false;
803
+ const rightBracket = value.indexOf("]", leftBracket);
804
+ const openParen = value.indexOf("(", rightBracket);
805
+ if (rightBracket === -1 || openParen === -1) return false;
806
+ if (!(leftBracket < rightBracket && rightBracket < openParen && openParen < rightParen))
807
+ return false;
808
+ const candidate = value.slice(leftBracket, rightParen + 1);
809
+ if (!/^\[([^\]]+)\]\(([^)]*)\)$/.test(candidate)) return false;
810
+ return start >= openParen + 1 && end <= rightParen;
811
+ }
812
+ /**
813
+ * Add keyboard event listeners to the textarea for undo/redo functionality.
814
+ */
815
+ addKeyboardListeners() {
816
+ const tryAddListener = (attempts = 0) => {
817
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
818
+ if (textarea) {
819
+ textarea.removeEventListener("keydown", this.handleKeyDown);
820
+ textarea.addEventListener("keydown", this.handleKeyDown);
821
+ } else if (attempts < 15) {
822
+ requestAnimationFrame(() => tryAddListener(attempts + 1));
823
+ }
824
+ };
825
+ tryAddListener();
826
+ }
827
+ /**
828
+ * Remove keyboard event listeners from the textarea to prevent memory leaks.
829
+ */
830
+ removeKeyboardListeners() {
831
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
832
+ if (textarea) {
833
+ textarea.removeEventListener("keydown", this.handleKeyDown);
834
+ }
835
+ }
836
+ /**
837
+ * Toggle color formatting by wrapping/unwrapping selection or current word.
838
+ *
839
+ * @param color - the hex color code to apply, e.g. '#FF0000' for red
840
+ */
841
+ toggleColorWrap(color) {
842
+ if (this.isReadonly || this.isDisabled) return;
843
+ this.saveUndoStateBeforeChange();
844
+ this.withSelection((textarea, start, end, value) => {
845
+ if (this.isSelectionInLinkUrl(start, end, value)) {
846
+ return;
847
+ }
848
+ const { start: s, end: e } = this.expandSelectionToWord(start, end, value);
849
+ let before = value.slice(0, s);
850
+ let selected = value.slice(s, e);
851
+ let after = value.slice(e);
852
+ const colorTagClose = "</span>";
853
+ const anyColorSpanRegex = /<span style="color: ([^"]+)">$/;
854
+ const beforeColorMatch = before.match(anyColorSpanRegex);
855
+ const hasOuterWrap = beforeColorMatch && after.startsWith(colorTagClose);
856
+ if (hasOuterWrap) {
857
+ const existingColor = beforeColorMatch[1];
858
+ let selStart2;
859
+ let selEnd2;
860
+ if (existingColor === color) {
861
+ before = before.slice(0, before.length - beforeColorMatch[0].length);
862
+ after = after.slice(colorTagClose.length);
863
+ this.value = before + selected + after;
864
+ selStart2 = before.length;
865
+ selEnd2 = selStart2 + selected.length;
866
+ } else {
867
+ const newColorTagOpen2 = `<span style="color: ${color}">`;
868
+ before = before.slice(0, before.length - beforeColorMatch[0].length) + newColorTagOpen2;
869
+ this.value = before + selected + after;
870
+ selStart2 = before.length;
871
+ selEnd2 = selStart2 + selected.length;
872
+ }
873
+ requestAnimationFrame(() => {
874
+ textarea.setSelectionRange(selStart2, selEnd2);
875
+ this.updateActiveFormats();
876
+ });
877
+ this.dispatchChange();
878
+ return;
879
+ }
880
+ const colorRegex = /^<span style="color: ([^"]+)">(.*)<\/span>$/s;
881
+ const innerMatch = selected.match(colorRegex);
882
+ if (innerMatch) {
883
+ const existingColor = innerMatch[1];
884
+ const innerText = innerMatch[2];
885
+ if (existingColor === color) {
886
+ selected = innerText;
887
+ } else {
888
+ selected = `<span style="color: ${color}">${innerText}</span>`;
889
+ }
890
+ this.value = before + selected + after;
891
+ const selStart2 = before.length;
892
+ const selEnd2 = selStart2 + (existingColor === color ? innerText.length : innerText.length);
893
+ requestAnimationFrame(() => {
894
+ textarea.setSelectionRange(selStart2, selEnd2);
895
+ this.updateActiveFormats();
896
+ });
897
+ this.dispatchChange();
898
+ return;
899
+ }
900
+ const newColorTagOpen = `<span style="color: ${color}">`;
901
+ const wrapped = `${newColorTagOpen}${selected || ""}${colorTagClose}`;
902
+ this.value = before + wrapped + after;
903
+ const selStart = before.length + newColorTagOpen.length;
904
+ const selEnd = selStart + (selected ? selected.length : 4);
905
+ requestAnimationFrame(() => {
906
+ textarea.setSelectionRange(selStart, selEnd);
907
+ this.updateActiveFormats();
908
+ });
909
+ this.dispatchChange();
910
+ });
911
+ }
912
+ /**
913
+ * Handle color selection from the dropdown.
914
+ *
915
+ * @param color - the selected hex color code
916
+ */
917
+ selectColor(color) {
918
+ if (this.savedSelection) {
919
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
920
+ if (textarea) {
921
+ textarea.focus();
922
+ textarea.setSelectionRange(
923
+ this.savedSelection.start,
924
+ this.savedSelection.end
925
+ );
926
+ this.currentSelection = this.savedSelection;
927
+ this.savedSelection = null;
928
+ this.toggleColorWrap(color);
929
+ }
930
+ } else {
931
+ this.toggleColorWrap(color);
932
+ }
933
+ }
934
+ /**
935
+ * Clear color formatting from the current selection or word.
936
+ */
937
+ clearColor() {
938
+ if (this.isReadonly || this.isDisabled) return;
939
+ this.saveUndoStateBeforeChange();
940
+ if (this.savedSelection) {
941
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
942
+ if (textarea) {
943
+ textarea.focus();
944
+ textarea.setSelectionRange(
945
+ this.savedSelection.start,
946
+ this.savedSelection.end
947
+ );
948
+ this.currentSelection = this.savedSelection;
949
+ this.savedSelection = null;
950
+ }
951
+ }
952
+ this.withSelection((ta, start, end, value) => {
953
+ if (this.isSelectionInLinkUrl(start, end, value)) {
954
+ return;
955
+ }
956
+ const { start: s, end: e } = this.expandSelectionToWord(start, end, value);
957
+ const before = value.slice(0, s);
958
+ let selected = value.slice(s, e);
959
+ const after = value.slice(e);
960
+ const colorRegex = /<span style="color: ([^"]+)">(.*?)<\/span>/gs;
961
+ selected = selected.replace(colorRegex, "$2");
962
+ const fullColorRegex = /<span style="color: ([^"]+)">(.*?)<\/span>/g;
963
+ let match;
964
+ let foundMatch = false;
965
+ const searchText = value.slice(
966
+ Math.max(0, s - 100),
967
+ Math.min(value.length, e + 100)
968
+ );
969
+ const searchOffset = Math.max(0, s - 100);
970
+ match = fullColorRegex.exec(searchText);
971
+ while (match !== null) {
972
+ const matchStart = searchOffset + match.index;
973
+ const matchEnd = searchOffset + match.index + match[0].length;
974
+ const spanOpenTag = `<span style="color: ${match[1]}">`;
975
+ const contentStart = searchOffset + match.index + spanOpenTag.length;
976
+ const contentEnd = matchEnd - 7;
977
+ if (contentStart <= s && e <= contentEnd) {
978
+ const beforeContent = value.slice(contentStart, s);
979
+ const afterContent = value.slice(e, contentEnd);
980
+ const newContent = beforeContent + selected + afterContent;
981
+ this.value = value.slice(0, matchStart) + newContent + value.slice(matchEnd);
982
+ const selStart = matchStart + beforeContent.length;
983
+ const selEnd = selStart + selected.length;
984
+ requestAnimationFrame(() => {
985
+ ta.setSelectionRange(selStart, selEnd);
986
+ this.updateActiveFormats();
987
+ });
988
+ this.dispatchChange();
989
+ foundMatch = true;
990
+ break;
991
+ }
992
+ match = fullColorRegex.exec(searchText);
993
+ }
994
+ if (!foundMatch) {
995
+ this.value = before + selected + after;
996
+ const selStart = before.length;
997
+ const selEnd = selStart + selected.length;
998
+ requestAnimationFrame(() => {
999
+ ta.setSelectionRange(selStart, selEnd);
1000
+ this.updateActiveFormats();
1001
+ });
1002
+ this.dispatchChange();
1003
+ }
1004
+ });
1005
+ }
1006
+ /**
1007
+ * Determine the current active heading level (1-3) based on activeFormats.
1008
+ */
1009
+ getActiveHeadingLevel() {
1010
+ if (this.activeFormats.h1) return 1;
1011
+ if (this.activeFormats.h2) return 2;
1012
+ if (this.activeFormats.h3) return 3;
1013
+ return 0;
1014
+ }
1015
+ /**
1016
+ * Save the initial state to the undo stack.
1017
+ */
1018
+ saveInitialUndoState() {
1019
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
1020
+ const initialSelection = textarea ? { start: textarea.selectionStart ?? 0, end: textarea.selectionEnd ?? 0 } : { start: 0, end: 0 };
1021
+ this.undoStack = [{ value: this.value, selection: initialSelection }];
1022
+ this.redoStack = [];
1023
+ this.lastSavedValue = this.value;
1024
+ }
1025
+ /**
1026
+ * Save the current state to the undo stack before making a change.
1027
+ *
1028
+ */
1029
+ saveUndoStateBeforeChange() {
1030
+ if (this.isUndoRedoAction) return;
1031
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
1032
+ const selection = textarea ? { start: textarea.selectionStart ?? 0, end: textarea.selectionEnd ?? 0 } : { start: 0, end: 0 };
1033
+ this.undoStack.push({
1034
+ value: this.value,
1035
+ selection
1036
+ });
1037
+ if (this.undoStack.length > this.MAX_UNDO_STACK_SIZE) {
1038
+ this.undoStack.shift();
1039
+ }
1040
+ this.redoStack = [];
1041
+ }
1042
+ /**
1043
+ * Save the current state to the undo stack if the value has changed.
1044
+ */
1045
+ saveUndoState() {
1046
+ if (this.isUndoRedoAction) return;
1047
+ if (this.value !== this.lastSavedValue) {
1048
+ this.undoStack.push({
1049
+ value: this.lastSavedValue,
1050
+ selection: this.currentSelection
1051
+ });
1052
+ if (this.undoStack.length > this.MAX_UNDO_STACK_SIZE) {
1053
+ this.undoStack.shift();
1054
+ }
1055
+ this.redoStack = [];
1056
+ this.lastSavedValue = this.value;
1057
+ }
1058
+ }
1059
+ /**
1060
+ * Schedule an undo state save after a short delay.
1061
+ */
1062
+ scheduleUndoStateSave() {
1063
+ if (this.undoTimeout) {
1064
+ clearTimeout(this.undoTimeout);
1065
+ }
1066
+ this.undoTimeout = window.setTimeout(() => {
1067
+ this.saveUndoState();
1068
+ this.undoTimeout = null;
1069
+ }, this.UNDO_SAVE_DELAY);
1070
+ }
1071
+ /**
1072
+ * Handle list continuation when Enter is pressed within a list item.
1073
+ * Works against the textarea value/selection.
1074
+ * Returns true if handled.
1075
+ */
1076
+ handleListContinuation() {
1077
+ const textarea = this.textareaEl?.shadowRoot?.querySelector(
1078
+ "textarea"
1079
+ );
1080
+ if (!textarea) return false;
1081
+ const start = textarea.selectionStart ?? 0;
1082
+ const end = textarea.selectionEnd ?? 0;
1083
+ if (start !== end) return false;
1084
+ const value = this.value;
1085
+ const lineStart = value.lastIndexOf("\n", start - 1) + 1;
1086
+ let lineEnd = value.indexOf("\n", start);
1087
+ if (lineEnd === -1) lineEnd = value.length;
1088
+ const currentLine = value.slice(lineStart, lineEnd);
1089
+ const unorderedMatch = currentLine.match(/^(\s*)([-*+])\s*(.*)$/);
1090
+ const orderedMatch = currentLine.match(/^(\s*)(\d+)\.\s*(.*)$/);
1091
+ if (!unorderedMatch && !orderedMatch) {
1092
+ return false;
1093
+ }
1094
+ this.saveUndoStateBeforeChange();
1095
+ if (unorderedMatch) {
1096
+ const indent = unorderedMatch[1] ?? "";
1097
+ const marker = unorderedMatch[2] ?? "-";
1098
+ const content = unorderedMatch[3] ?? "";
1099
+ if (content.trim() === "") {
1100
+ const before2 = value.slice(0, lineStart);
1101
+ const after2 = value.slice(lineEnd);
1102
+ const newValue2 = before2 + (lineEnd === value.length ? "" : "") + after2;
1103
+ this.value = newValue2;
1104
+ textarea.value = newValue2;
1105
+ const newCursor2 = before2.length;
1106
+ requestAnimationFrame(() => {
1107
+ textarea.setSelectionRange(newCursor2, newCursor2);
1108
+ this.updateActiveFormats();
1109
+ });
1110
+ this.dispatchChange();
1111
+ return true;
1112
+ }
1113
+ const before = value.slice(0, start);
1114
+ const after = value.slice(start);
1115
+ const prefix = `
1116
+ ${indent}${marker} `;
1117
+ const newValue = before + prefix + after;
1118
+ this.value = newValue;
1119
+ textarea.value = newValue;
1120
+ const newCursor = start + prefix.length;
1121
+ requestAnimationFrame(() => {
1122
+ textarea.setSelectionRange(newCursor, newCursor);
1123
+ this.updateActiveFormats();
1124
+ });
1125
+ this.dispatchChange();
1126
+ return true;
1127
+ }
1128
+ if (orderedMatch) {
1129
+ const indent = orderedMatch[1] ?? "";
1130
+ const numberStr = orderedMatch[2] ?? "1";
1131
+ const content = orderedMatch[3] ?? "";
1132
+ if (content.trim() === "") {
1133
+ const before2 = value.slice(0, lineStart);
1134
+ const after2 = value.slice(lineEnd);
1135
+ const newValue2 = before2 + (lineEnd === value.length ? "" : "") + after2;
1136
+ this.value = newValue2;
1137
+ textarea.value = newValue2;
1138
+ const newCursor2 = before2.length;
1139
+ requestAnimationFrame(() => {
1140
+ textarea.setSelectionRange(newCursor2, newCursor2);
1141
+ this.updateActiveFormats();
1142
+ });
1143
+ this.dispatchChange();
1144
+ return true;
1145
+ }
1146
+ let nextNumber = parseInt(numberStr, 10) + 1;
1147
+ const beforeText = value.slice(0, lineStart);
1148
+ const beforeLines = beforeText.split("\n");
1149
+ let lastNumberAtThisLevel = 0;
1150
+ for (let i = beforeLines.length - 1; i >= 0; i--) {
1151
+ const line = beforeLines[i];
1152
+ const match = line.match(/^(\s*)(\d+)\.\s*(.*)$/);
1153
+ if (match && match[1] === indent) {
1154
+ lastNumberAtThisLevel = parseInt(match[2], 10);
1155
+ break;
1156
+ } else if (line.trim() !== "" && !line.match(/^\s*[-*+]\s+/) && !match) {
1157
+ break;
1158
+ }
1159
+ }
1160
+ if (lastNumberAtThisLevel > 0) {
1161
+ nextNumber = lastNumberAtThisLevel + 1;
1162
+ }
1163
+ const before = value.slice(0, start);
1164
+ const after = value.slice(start);
1165
+ const prefix = `
1166
+ ${indent}${nextNumber}. `;
1167
+ const newValue = before + prefix + after;
1168
+ const renumberedValue = this.renumberOrderedListItems(
1169
+ newValue,
1170
+ start + prefix.length,
1171
+ indent
1172
+ );
1173
+ this.value = renumberedValue;
1174
+ textarea.value = renumberedValue;
1175
+ const newCursor = start + prefix.length;
1176
+ requestAnimationFrame(() => {
1177
+ textarea.setSelectionRange(newCursor, newCursor);
1178
+ this.updateActiveFormats();
1179
+ });
1180
+ this.dispatchChange();
1181
+ return true;
1182
+ }
1183
+ return false;
1184
+ }
1185
+ /**
1186
+ * Handle Tab key to indent a list item (increase nesting level).
1187
+ * Returns true if handled.
1188
+ */
1189
+ handleListIndent() {
1190
+ const textarea = this.textareaEl?.shadowRoot?.querySelector(
1191
+ "textarea"
1192
+ );
1193
+ if (!textarea) return false;
1194
+ const start = textarea.selectionStart ?? 0;
1195
+ const end = textarea.selectionEnd ?? 0;
1196
+ if (start !== end) return false;
1197
+ const value = this.value;
1198
+ const lineStart = value.lastIndexOf("\n", start - 1) + 1;
1199
+ let lineEnd = value.indexOf("\n", start);
1200
+ if (lineEnd === -1) lineEnd = value.length;
1201
+ const currentLine = value.slice(lineStart, lineEnd);
1202
+ const unorderedMatch = currentLine.match(/^(\s*)([-*+])\s*(.*)$/);
1203
+ const orderedMatch = currentLine.match(/^(\s*)(\d+)\.\s*(.*)$/);
1204
+ if (!unorderedMatch && !orderedMatch) {
1205
+ return false;
1206
+ }
1207
+ this.saveUndoStateBeforeChange();
1208
+ const before = value.slice(0, lineStart);
1209
+ const after = value.slice(lineEnd);
1210
+ const indent = " ";
1211
+ let newLine;
1212
+ let newCursorOffset = 0;
1213
+ if (unorderedMatch) {
1214
+ const currentIndent = unorderedMatch[1] ?? "";
1215
+ const marker = unorderedMatch[2] ?? "-";
1216
+ const content = unorderedMatch[3] ?? "";
1217
+ newLine = `${currentIndent}${indent}${marker} ${content}`;
1218
+ newCursorOffset = indent.length;
1219
+ } else if (orderedMatch) {
1220
+ const currentIndent = orderedMatch[1] ?? "";
1221
+ const content = orderedMatch[3] ?? "";
1222
+ newLine = `${currentIndent}${indent}1. ${content}`;
1223
+ newCursorOffset = indent.length;
1224
+ } else {
1225
+ return false;
1226
+ }
1227
+ const newValue = before + newLine + after;
1228
+ this.value = newValue;
1229
+ textarea.value = newValue;
1230
+ const cursorInLine = start - lineStart;
1231
+ const newCursor = lineStart + cursorInLine + newCursorOffset;
1232
+ requestAnimationFrame(() => {
1233
+ textarea.setSelectionRange(newCursor, newCursor);
1234
+ this.updateActiveFormats();
1235
+ });
1236
+ this.dispatchChange();
1237
+ return true;
1238
+ }
1239
+ /**
1240
+ * Handle Shift+Tab key to outdent a list item (decrease nesting level).
1241
+ * Returns true if handled.
1242
+ */
1243
+ handleListOutdent() {
1244
+ const textarea = this.textareaEl?.shadowRoot?.querySelector(
1245
+ "textarea"
1246
+ );
1247
+ if (!textarea) return false;
1248
+ const start = textarea.selectionStart ?? 0;
1249
+ const end = textarea.selectionEnd ?? 0;
1250
+ if (start !== end) return false;
1251
+ const value = this.value;
1252
+ const lineStart = value.lastIndexOf("\n", start - 1) + 1;
1253
+ let lineEnd = value.indexOf("\n", start);
1254
+ if (lineEnd === -1) lineEnd = value.length;
1255
+ const currentLine = value.slice(lineStart, lineEnd);
1256
+ const unorderedMatch = currentLine.match(/^(\s*)([-*+])\s*(.*)$/);
1257
+ const orderedMatch = currentLine.match(/^(\s*)(\d+)\.\s*(.*)$/);
1258
+ if (!unorderedMatch && !orderedMatch) {
1259
+ return false;
1260
+ }
1261
+ const currentIndent = (unorderedMatch || orderedMatch)?.[1] ?? "";
1262
+ if (currentIndent.length < 4) {
1263
+ return false;
1264
+ }
1265
+ this.saveUndoStateBeforeChange();
1266
+ const before = value.slice(0, lineStart);
1267
+ const after = value.slice(lineEnd);
1268
+ const outdent = " ";
1269
+ let newLine;
1270
+ let newCursorOffset = 0;
1271
+ if (unorderedMatch) {
1272
+ const marker = unorderedMatch[2] ?? "-";
1273
+ const content = unorderedMatch[3] ?? "";
1274
+ const newIndent = currentIndent.slice(outdent.length);
1275
+ newLine = `${newIndent}${marker} ${content}`;
1276
+ newCursorOffset = -outdent.length;
1277
+ } else if (orderedMatch) {
1278
+ const content = orderedMatch[3] ?? "";
1279
+ const newIndent = currentIndent.slice(outdent.length);
1280
+ let newNumber = 1;
1281
+ const beforeLines = before.split("\n");
1282
+ for (let i = beforeLines.length - 1; i >= 0; i--) {
1283
+ const line = beforeLines[i];
1284
+ const match = line.match(/^(\s*)(\d+)\.\s*(.*)$/);
1285
+ if (match && match[1] === newIndent) {
1286
+ newNumber = parseInt(match[2], 10) + 1;
1287
+ break;
1288
+ } else if (line.trim() !== "" && !line.match(/^\s*[-*+]\s+/) && !match) {
1289
+ break;
1290
+ }
1291
+ }
1292
+ newLine = `${newIndent}${newNumber}. ${content}`;
1293
+ newCursorOffset = -outdent.length;
1294
+ } else {
1295
+ return false;
1296
+ }
1297
+ let newValue = before + newLine + after;
1298
+ if (orderedMatch) {
1299
+ const newIndent = currentIndent.slice(outdent.length);
1300
+ newValue = this.renumberOrderedListItems(
1301
+ newValue,
1302
+ lineStart + newLine.length,
1303
+ newIndent
1304
+ );
1305
+ }
1306
+ this.value = newValue;
1307
+ textarea.value = newValue;
1308
+ const cursorInLine = start - lineStart;
1309
+ const newCursor = Math.max(
1310
+ lineStart,
1311
+ lineStart + cursorInLine + newCursorOffset
1312
+ );
1313
+ requestAnimationFrame(() => {
1314
+ textarea.setSelectionRange(newCursor, newCursor);
1315
+ this.updateActiveFormats();
1316
+ });
1317
+ this.dispatchChange();
1318
+ return true;
1319
+ }
1320
+ /**
1321
+ * Renumber ordered list items that come after the given position with the same indentation level.
1322
+ *
1323
+ * @param value - The text content to process
1324
+ * @param fromPosition - Start looking for list items from this position
1325
+ * @param targetIndent - Only renumber items with this exact indentation
1326
+ * @returns The text with renumbered list items
1327
+ */
1328
+ renumberOrderedListItems(value, fromPosition, targetIndent) {
1329
+ const lines = value.split("\n");
1330
+ const fromLineIndex = Math.max(
1331
+ 0,
1332
+ value.slice(0, fromPosition).split("\n").length - 1
1333
+ );
1334
+ let nextExpectedNumber = 1;
1335
+ const currentLine = lines[fromLineIndex];
1336
+ const currentMatch = currentLine?.match(/^(\s*)(\d+)\.\s*(.*)$/);
1337
+ if (currentMatch && currentMatch[1] === targetIndent) {
1338
+ nextExpectedNumber = parseInt(currentMatch[2], 10) + 1;
1339
+ } else {
1340
+ for (let i = fromLineIndex - 1; i >= 0; i--) {
1341
+ const line = lines[i];
1342
+ const orderedMatch = line.match(/^(\s*)(\d+)\.\s*(.*)$/);
1343
+ if (orderedMatch && orderedMatch[1] === targetIndent) {
1344
+ nextExpectedNumber = parseInt(orderedMatch[2], 10) + 1;
1345
+ break;
1346
+ }
1347
+ }
1348
+ }
1349
+ let currentNumber = nextExpectedNumber;
1350
+ const orderedRegex = /^(\s*)(\d+)\.\s*(.*)$/;
1351
+ for (let i = fromLineIndex + 1; i < lines.length; i++) {
1352
+ const line = lines[i];
1353
+ const orderedMatch = line.match(orderedRegex);
1354
+ if (orderedMatch && orderedMatch[1] === targetIndent) {
1355
+ const indent = orderedMatch[1];
1356
+ const content = orderedMatch[3];
1357
+ lines[i] = `${indent}${currentNumber}. ${content}`;
1358
+ currentNumber++;
1359
+ } else if (orderedMatch && orderedMatch[1].length < targetIndent.length) {
1360
+ break;
1361
+ } else if (line.trim() !== "" && !orderedMatch && !line.match(/^\s*[-*+]\s+/)) {
1362
+ if (targetIndent === "") {
1363
+ break;
1364
+ }
1365
+ }
1366
+ }
1367
+ return lines.join("\n");
1368
+ }
1369
+ /**
1370
+ * Renumber any ordered lists that come after the given position, starting each new sequence from 1.
1371
+ * This is used when removing list formatting breaks the continuity of a list.
1372
+ *
1373
+ * @param value - The text content to process
1374
+ * @param fromPosition - Start looking for list items from this position
1375
+ * @returns The text with renumbered list sequences
1376
+ */
1377
+ renumberSubsequentOrderedLists(value, fromPosition) {
1378
+ const lines = value.split("\n");
1379
+ const fromLineIndex = Math.max(
1380
+ 0,
1381
+ value.slice(0, fromPosition).split("\n").length - 1
1382
+ );
1383
+ const orderedRegex = /^(\s*)(\d+)\.\s*(.*)$/;
1384
+ let currentSequenceNumber = 1;
1385
+ let inSequence = false;
1386
+ let currentIndent = "";
1387
+ for (let i = fromLineIndex + 1; i < lines.length; i++) {
1388
+ const line = lines[i];
1389
+ const orderedMatch = line.match(orderedRegex);
1390
+ if (orderedMatch) {
1391
+ const indent = orderedMatch[1];
1392
+ const content = orderedMatch[3];
1393
+ if (!inSequence || indent !== currentIndent) {
1394
+ currentSequenceNumber = 1;
1395
+ inSequence = true;
1396
+ currentIndent = indent;
1397
+ }
1398
+ lines[i] = `${indent}${currentSequenceNumber}. ${content}`;
1399
+ currentSequenceNumber++;
1400
+ } else if (line.trim() !== "" && !line.match(/^\s*[-*+]\s+/)) {
1401
+ inSequence = false;
1402
+ }
1403
+ }
1404
+ return lines.join("\n");
1405
+ }
1406
+ /**
1407
+ * Handle backspace to remove empty list items.
1408
+ * Returns true if handled.
1409
+ */
1410
+ handleListBackspace() {
1411
+ const textarea = this.textareaEl?.shadowRoot?.querySelector(
1412
+ "textarea"
1413
+ );
1414
+ if (!textarea) return false;
1415
+ const start = textarea.selectionStart ?? 0;
1416
+ const end = textarea.selectionEnd ?? 0;
1417
+ if (start !== end) return false;
1418
+ const value = this.value;
1419
+ const lineStart = value.lastIndexOf("\n", start - 1) + 1;
1420
+ let lineEnd = value.indexOf("\n", start);
1421
+ if (lineEnd === -1) lineEnd = value.length;
1422
+ const currentLine = value.slice(lineStart, lineEnd);
1423
+ const cursorPositionInLine = start - lineStart;
1424
+ const unorderedMatch = currentLine.match(/^(\s*)([-*+])\s*(.*)$/);
1425
+ const orderedMatch = currentLine.match(/^(\s*)(\d+)\.\s*(.*)$/);
1426
+ if (!unorderedMatch && !orderedMatch) {
1427
+ return false;
1428
+ }
1429
+ let markerEndPosition = 0;
1430
+ let hasContent = false;
1431
+ if (unorderedMatch) {
1432
+ const indent = unorderedMatch[1] ?? "";
1433
+ const marker = unorderedMatch[2] ?? "-";
1434
+ const content = unorderedMatch[3] ?? "";
1435
+ markerEndPosition = indent.length + marker.length + 1;
1436
+ hasContent = content.trim().length > 0;
1437
+ } else if (orderedMatch) {
1438
+ const indent = orderedMatch[1] ?? "";
1439
+ const numberStr = orderedMatch[2] ?? "1";
1440
+ const content = orderedMatch[3] ?? "";
1441
+ markerEndPosition = indent.length + numberStr.length + 2;
1442
+ hasContent = content.trim().length > 0;
1443
+ }
1444
+ if (cursorPositionInLine === markerEndPosition && !hasContent) {
1445
+ this.saveUndoStateBeforeChange();
1446
+ const before = value.slice(0, lineStart);
1447
+ const after = value.slice(
1448
+ lineEnd === value.length ? lineEnd : lineEnd + 1
1449
+ );
1450
+ let newValue = before + after;
1451
+ if (orderedMatch) {
1452
+ const indent = orderedMatch[1] ?? "";
1453
+ newValue = this.renumberOrderedListItems(
1454
+ newValue,
1455
+ before.length,
1456
+ indent
1457
+ );
1458
+ }
1459
+ this.value = newValue;
1460
+ textarea.value = newValue;
1461
+ let newCursor = before.length;
1462
+ if (before.endsWith("\n") && before.length > 1) {
1463
+ newCursor = before.length - 1;
1464
+ }
1465
+ requestAnimationFrame(() => {
1466
+ textarea.setSelectionRange(newCursor, newCursor);
1467
+ this.updateActiveFormats();
1468
+ });
1469
+ this.dispatchChange();
1470
+ return true;
1471
+ }
1472
+ return false;
1473
+ }
1474
+ /**
1475
+ /**
1476
+ * Perform an undo operation, reverting to the previous state.
1477
+ */
1478
+ undo() {
1479
+ if (this.isReadonly || this.isDisabled || this.undoStack.length <= 1) {
1480
+ return;
1481
+ }
1482
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
1483
+ const currentSelection = textarea ? { start: textarea.selectionStart ?? 0, end: textarea.selectionEnd ?? 0 } : { start: 0, end: 0 };
1484
+ this.redoStack.push({ value: this.value, selection: currentSelection });
1485
+ const previousState = this.undoStack.pop();
1486
+ if (!previousState) {
1487
+ return;
1488
+ }
1489
+ this.isUndoRedoAction = true;
1490
+ this.value = previousState.value;
1491
+ this.lastSavedValue = previousState.value;
1492
+ if (textarea) {
1493
+ textarea.value = previousState.value;
1494
+ }
1495
+ requestAnimationFrame(() => {
1496
+ if (textarea) {
1497
+ textarea.setSelectionRange(
1498
+ previousState.selection.start,
1499
+ previousState.selection.end
1500
+ );
1501
+ }
1502
+ this.updateActiveFormats();
1503
+ this.isUndoRedoAction = false;
1504
+ });
1505
+ this.dispatchChange();
1506
+ }
1507
+ /**
1508
+ * Perform a redo operation, reapplying a previously undone state.
1509
+ */
1510
+ redo() {
1511
+ if (this.isReadonly || this.isDisabled || this.redoStack.length === 0) {
1512
+ return;
1513
+ }
1514
+ const textarea = this.textareaEl?.shadowRoot?.querySelector("textarea");
1515
+ const currentSelection = textarea ? { start: textarea.selectionStart ?? 0, end: textarea.selectionEnd ?? 0 } : { start: 0, end: 0 };
1516
+ this.undoStack.push({ value: this.value, selection: currentSelection });
1517
+ const nextState = this.redoStack.pop();
1518
+ if (!nextState) {
1519
+ return;
1520
+ }
1521
+ this.isUndoRedoAction = true;
1522
+ this.value = nextState.value;
1523
+ this.lastSavedValue = nextState.value;
1524
+ if (textarea) {
1525
+ textarea.value = nextState.value;
1526
+ }
1527
+ requestAnimationFrame(() => {
1528
+ if (textarea) {
1529
+ textarea.setSelectionRange(
1530
+ nextState.selection.start,
1531
+ nextState.selection.end
1532
+ );
1533
+ }
1534
+ this.updateActiveFormats();
1535
+ this.isUndoRedoAction = false;
1536
+ });
1537
+ this.dispatchChange();
1538
+ }
1539
+ connectedCallback() {
1540
+ super.connectedCallback();
1541
+ document.addEventListener("click", this.handleOutsideClick);
1542
+ }
1543
+ firstUpdated(changedProperties) {
1544
+ super.firstUpdated(changedProperties);
1545
+ requestAnimationFrame(() => {
1546
+ this.saveInitialUndoState();
1547
+ this.updateActiveFormats();
1548
+ this.addKeyboardListeners();
1549
+ });
1550
+ }
1551
+ disconnectedCallback() {
1552
+ super.disconnectedCallback();
1553
+ document.removeEventListener("click", this.handleOutsideClick);
1554
+ if (this.undoTimeout) {
1555
+ clearTimeout(this.undoTimeout);
1556
+ }
1557
+ this.removeKeyboardListeners();
1558
+ this.savedSelectionForPreview = null;
1559
+ }
1560
+ labelTemplate() {
1561
+ return this.label ? x`<label class="heading-inter-14-bold text-neutral-20 block"
1562
+ >${this.label}</label
1563
+ >` : E;
1564
+ }
1565
+ descriptionTemplate() {
1566
+ return this.description ? x`<div class="paragraph-inter-12-regular text-neutral-20">
1567
+ <lukso-sanitize html-content=${this.description}></lukso-sanitize>
1568
+ </div>` : E;
1569
+ }
1570
+ /**
1571
+ * Restore focus and selection to the textarea after toolbar interactions.
1572
+ */
1573
+ restoreFocusAndSelection() {
1574
+ const ta = this.textareaEl?.shadowRoot?.querySelector(
1575
+ "textarea"
1576
+ );
1577
+ if (ta) {
1578
+ ta.focus();
1579
+ const sel = this.currentSelection;
1580
+ const start = typeof sel.start === "number" ? sel.start : ta.selectionStart ?? 0;
1581
+ const end = typeof sel.end === "number" ? sel.end : ta.selectionEnd ?? 0;
1582
+ ta.setSelectionRange(start, end);
1583
+ }
1584
+ }
1585
+ buttonTemplate(icon, handler, name, isActive = false) {
1586
+ return x`
1587
+ <lukso-tooltip text=${name} placement="top">
1588
+ <lukso-button
1589
+ @click=${() => {
1590
+ this.restoreFocusAndSelection();
1591
+ handler();
1592
+ }}
1593
+ aria-label=${name}
1594
+ aria-pressed=${isActive ? "true" : "false"}
1595
+ type="button"
1596
+ variant="secondary"
1597
+ size="small"
1598
+ custom-class=${this.toolbarButton({ active: isActive })}
1599
+ is-icon
1600
+ >
1601
+ <lukso-icon
1602
+ name=${icon}
1603
+ size="small"
1604
+ pack="vuesax"
1605
+ variant="linear"
1606
+ ></lukso-icon></lukso-button
1607
+ ></lukso-tooltip>
1608
+ `;
1609
+ }
1610
+ toolbarTemplate() {
1611
+ return x`
1612
+ <div class="flex items-center gap-2">
1613
+ <div class=${cn(this.styles().headingMenu())}>
1614
+ <!-- Heading -->
1615
+ <lukso-tooltip text="Heading options" placement="top">
1616
+ <lukso-button
1617
+ id=${this.headingTriggerId}
1618
+ @click=${(e) => {
1619
+ e.stopPropagation();
1620
+ this.isColorDropdownOpen = false;
1621
+ this.isListDropdownOpen = false;
1622
+ this.isHeadingDropdownOpen = !this.isHeadingDropdownOpen;
1623
+ }}
1624
+ aria-expanded=${this.isHeadingDropdownOpen ? "true" : "false"}
1625
+ aria-label="Heading options"
1626
+ variant="secondary"
1627
+ size="small"
1628
+ custom-class=${this.toolbarButton({
1629
+ active: this.getActiveHeadingLevel() > 0
1630
+ })}
1631
+ is-icon
1632
+ >
1633
+ <lukso-icon
1634
+ name="smallcaps"
1635
+ size="small"
1636
+ pack="vuesax"
1637
+ variant="linear"
1638
+ ></lukso-icon>
1639
+ </lukso-button>
1640
+ </lukso-tooltip>
1641
+ <lukso-dropdown
1642
+ id="headingDropdown"
1643
+ trigger-id=""
1644
+ size="medium"
1645
+ ?is-open=${this.isHeadingDropdownOpen}
1646
+ >
1647
+ <lukso-dropdown-option
1648
+ ?is-selected=${this.getActiveHeadingLevel() === 0}
1649
+ @click=${(e) => {
1650
+ e.stopPropagation();
1651
+ this.restoreFocusAndSelection();
1652
+ this.applyHeading(0);
1653
+ this.isHeadingDropdownOpen = false;
1654
+ }}
1655
+ size="medium"
1656
+ >
1657
+ Normal text
1658
+ </lukso-dropdown-option>
1659
+ <lukso-dropdown-option
1660
+ ?is-selected=${this.getActiveHeadingLevel() === 1}
1661
+ @click=${(e) => {
1662
+ e.stopPropagation();
1663
+ this.restoreFocusAndSelection();
1664
+ this.applyHeading(1);
1665
+ this.isHeadingDropdownOpen = false;
1666
+ }}
1667
+ size="medium"
1668
+ >
1669
+ Heading 1
1670
+ </lukso-dropdown-option>
1671
+ <lukso-dropdown-option
1672
+ ?is-selected=${this.getActiveHeadingLevel() === 2}
1673
+ @click=${(e) => {
1674
+ e.stopPropagation();
1675
+ this.restoreFocusAndSelection();
1676
+ this.applyHeading(2);
1677
+ this.isHeadingDropdownOpen = false;
1678
+ }}
1679
+ size="medium"
1680
+ >
1681
+ Heading 2
1682
+ </lukso-dropdown-option>
1683
+ <lukso-dropdown-option
1684
+ ?is-selected=${this.getActiveHeadingLevel() === 3}
1685
+ @click=${(e) => {
1686
+ e.stopPropagation();
1687
+ this.restoreFocusAndSelection();
1688
+ this.applyHeading(3);
1689
+ this.isHeadingDropdownOpen = false;
1690
+ }}
1691
+ size="medium"
1692
+ >
1693
+ Heading 3
1694
+ </lukso-dropdown-option>
1695
+ </lukso-dropdown>
1696
+ </div>
1697
+
1698
+ <!-- Bold -->
1699
+ ${this.buttonTemplate(
1700
+ "text-bold",
1701
+ () => this.toggleWrap("**"),
1702
+ "Bold",
1703
+ this.activeFormats.bold
1704
+ )}
1705
+
1706
+ <!-- Italic -->
1707
+ ${this.buttonTemplate(
1708
+ "text-italic",
1709
+ () => this.toggleWrap("*"),
1710
+ "Italic",
1711
+ this.activeFormats.italic
1712
+ )}
1713
+
1714
+ <!-- List -->
1715
+ <div class=${this.styles().listMenu()}>
1716
+ <lukso-tooltip text="List options" placement="top">
1717
+ <lukso-button
1718
+ id=${this.listTriggerId}
1719
+ @click=${(e) => {
1720
+ e.stopPropagation();
1721
+ this.restoreFocusAndSelection();
1722
+ this.isHeadingDropdownOpen = false;
1723
+ this.isColorDropdownOpen = false;
1724
+ this.isListDropdownOpen = !this.isListDropdownOpen;
1725
+ }}
1726
+ aria-expanded=${this.isListDropdownOpen ? "true" : "false"}
1727
+ aria-label="List options"
1728
+ variant="secondary"
1729
+ size="small"
1730
+ custom-class=${this.toolbarButton({
1731
+ active: this.activeFormats.unorderedList || this.activeFormats.orderedList
1732
+ })}
1733
+ is-icon
1734
+ >
1735
+ <lukso-icon
1736
+ name="task"
1737
+ size="small"
1738
+ pack="vuesax"
1739
+ variant="linear"
1740
+ ></lukso-icon>
1741
+ </lukso-button>
1742
+ </lukso-tooltip>
1743
+ <lukso-dropdown
1744
+ id="listDropdown"
1745
+ trigger-id=""
1746
+ size="medium"
1747
+ ?is-open=${this.isListDropdownOpen}
1748
+ >
1749
+ <lukso-dropdown-option
1750
+ ?is-selected=${this.getActiveListType() === "none"}
1751
+ @click=${(e) => {
1752
+ e.stopPropagation();
1753
+ this.restoreFocusAndSelection();
1754
+ this.applyList("none");
1755
+ this.isListDropdownOpen = false;
1756
+ }}
1757
+ size="medium"
1758
+ >
1759
+ No list
1760
+ </lukso-dropdown-option>
1761
+ <lukso-dropdown-option
1762
+ ?is-selected=${this.getActiveListType() === "unordered"}
1763
+ @click=${(e) => {
1764
+ e.stopPropagation();
1765
+ this.restoreFocusAndSelection();
1766
+ this.applyList("unordered");
1767
+ this.isListDropdownOpen = false;
1768
+ }}
1769
+ size="medium"
1770
+ >
1771
+ Unordered
1772
+ </lukso-dropdown-option>
1773
+ <lukso-dropdown-option
1774
+ ?is-selected=${this.getActiveListType() === "ordered"}
1775
+ @click=${(e) => {
1776
+ e.stopPropagation();
1777
+ this.restoreFocusAndSelection();
1778
+ this.applyList("ordered");
1779
+ this.isListDropdownOpen = false;
1780
+ }}
1781
+ size="medium"
1782
+ >
1783
+ Ordered
1784
+ </lukso-dropdown-option>
1785
+ </lukso-dropdown>
1786
+ </div>
1787
+
1788
+ <!-- Link -->
1789
+ ${this.buttonTemplate(
1790
+ "link",
1791
+ () => this.insertLink(),
1792
+ "Link",
1793
+ this.activeFormats.link
1794
+ )}
1795
+
1796
+ <!-- Color -->
1797
+ <div class=${this.styles().colorMenu()}>
1798
+ <lukso-tooltip text="Text color" placement="top">
1799
+ <lukso-button
1800
+ id=${this.colorTriggerId}
1801
+ @click=${(e) => {
1802
+ e.stopPropagation();
1803
+ this.restoreFocusAndSelection();
1804
+ this.isHeadingDropdownOpen = false;
1805
+ this.isListDropdownOpen = false;
1806
+ if (!this.isColorDropdownOpen) {
1807
+ const ta = this.textareaEl?.shadowRoot?.querySelector("textarea");
1808
+ if (ta) {
1809
+ this.savedSelection = {
1810
+ start: ta.selectionStart ?? 0,
1811
+ end: ta.selectionEnd ?? 0
1812
+ };
1813
+ }
1814
+ }
1815
+ this.isColorDropdownOpen = !this.isColorDropdownOpen;
1816
+ }}
1817
+ aria-expanded=${this.isColorDropdownOpen ? "true" : "false"}
1818
+ aria-pressed=${this.activeFormats.color ? "true" : "false"}
1819
+ aria-label="Text color"
1820
+ variant="secondary"
1821
+ size="small"
1822
+ custom-class=${this.toolbarButton({
1823
+ active: this.activeFormats.color
1824
+ })}
1825
+ is-icon
1826
+ >
1827
+ <div
1828
+ class="size-4 rounded-full"
1829
+ style="background-color: ${this.activeFormats.activeColor};"
1830
+ ></div>
1831
+ </lukso-button>
1832
+ </lukso-tooltip>
1833
+ <lukso-dropdown
1834
+ id="colorDropdown"
1835
+ trigger-id=""
1836
+ size="medium"
1837
+ max-height="300"
1838
+ ?is-open=${this.isColorDropdownOpen}
1839
+ >
1840
+ <div class="grid grid-cols-8 gap-2 p-2 w-[260px]">
1841
+ <div class="col-span-8 mb-2 flex items-center justify-between">
1842
+ <span class="text-xs text-neutral-60">Text Color</span>
1843
+ ${this.activeFormats.color ? x`<button
1844
+ class="text-xs text-neutral-60 hover:text-neutral-20 underline"
1845
+ @click=${(e) => {
1846
+ e.stopPropagation();
1847
+ this.clearColor();
1848
+ this.isColorDropdownOpen = false;
1849
+ }}
1850
+ type="button"
1851
+ >
1852
+ Clear
1853
+ </button>` : E}
1854
+ </div>
1855
+ ${this.colorSamples.map(
1856
+ (color) => x`
1857
+ <button
1858
+ class="w-6 h-6 rounded-4 border transition-all ${this.activeFormats.activeColor === color ? "border-neutral-20 ring-2 ring-purple-51" : "border-neutral-90 hover:border-neutral-60"}"
1859
+ style="background-color: ${color}"
1860
+ title=${color}
1861
+ aria-pressed=${this.activeFormats.activeColor === color ? "true" : "false"}
1862
+ @click=${(e) => {
1863
+ e.stopPropagation();
1864
+ this.selectColor(color);
1865
+ this.isColorDropdownOpen = false;
1866
+ }}
1867
+ ></button>
1868
+ `
1869
+ )}
1870
+ </div>
1871
+ </lukso-dropdown>
1872
+ </div>
1873
+
1874
+ <div class=${this.styles().divider()}></div>
1875
+ </div>
1876
+ `;
1877
+ }
1878
+ render() {
1879
+ const { wrapper, header, toolbar, area, editor, preview } = this.styles({
1880
+ isFullWidth: this.isFullWidth
1881
+ });
1882
+ return x`
1883
+ <div class=${wrapper()}>
1884
+ ${this.labelTemplate()} ${this.descriptionTemplate()}
1885
+
1886
+ <div class=${header()}>
1887
+ <div class=${toolbar()}>${this.toolbarTemplate()}</div>
1888
+ ${this.buttonTemplate(
1889
+ "eye",
1890
+ () => this.togglePreview(),
1891
+ "Toggle preview",
1892
+ this.isPreview
1893
+ )}
1894
+ </div>
1895
+
1896
+ <div class=${area()}>
1897
+ ${!this.isPreview ? x`<div class=${editor()}>
1898
+ <lukso-textarea
1899
+ .value=${this.value}
1900
+ name=${this.name ? this.name : E}
1901
+ size=${this.size ? this.size : E}
1902
+ rows=${this.rows ? this.rows : E}
1903
+ placeholder=${this.placeholder ? this.placeholder : E}
1904
+ error=${this.error ? this.error : E}
1905
+ ?is-full-width=${true}
1906
+ ?is-disabled=${this.isDisabled}
1907
+ ?is-readonly=${this.isReadonly}
1908
+ ?is-non-resizable=${this.isNonResizable}
1909
+ @on-input=${this.handleTextareaInput}
1910
+ @on-key-up=${this.handleTextareaKeyUp}
1911
+ @on-input-click=${this.handleTextareaClick}
1912
+ ></lukso-textarea>
1913
+ </div>` : x`<div class=${preview()}>
1914
+ <lukso-markdown
1915
+ value=${this.value}
1916
+ prose-classes="prose prose-base prose-gray"
1917
+ ></lukso-markdown>
1918
+ </div>`}
1919
+ </div>
1920
+ </div>
1921
+ `;
1922
+ }
1923
+ };
1924
+ __decorateClass([
1925
+ n({ type: String })
1926
+ ], LuksoMarkdownEditor.prototype, "value", 2);
1927
+ __decorateClass([
1928
+ n({ type: String })
1929
+ ], LuksoMarkdownEditor.prototype, "name", 2);
1930
+ __decorateClass([
1931
+ n({ type: String })
1932
+ ], LuksoMarkdownEditor.prototype, "label", 2);
1933
+ __decorateClass([
1934
+ n({ type: String })
1935
+ ], LuksoMarkdownEditor.prototype, "description", 2);
1936
+ __decorateClass([
1937
+ n({ type: String })
1938
+ ], LuksoMarkdownEditor.prototype, "error", 2);
1939
+ __decorateClass([
1940
+ n({ type: Boolean, attribute: "is-full-width" })
1941
+ ], LuksoMarkdownEditor.prototype, "isFullWidth", 2);
1942
+ __decorateClass([
1943
+ n({ type: Boolean, attribute: "is-readonly" })
1944
+ ], LuksoMarkdownEditor.prototype, "isReadonly", 2);
1945
+ __decorateClass([
1946
+ n({ type: Boolean, attribute: "is-disabled" })
1947
+ ], LuksoMarkdownEditor.prototype, "isDisabled", 2);
1948
+ __decorateClass([
1949
+ n({ type: Boolean, attribute: "is-non-resizable" })
1950
+ ], LuksoMarkdownEditor.prototype, "isNonResizable", 2);
1951
+ __decorateClass([
1952
+ n({ type: Boolean })
1953
+ ], LuksoMarkdownEditor.prototype, "autofocus", 2);
1954
+ __decorateClass([
1955
+ n({ type: String, reflect: true })
1956
+ ], LuksoMarkdownEditor.prototype, "size", 2);
1957
+ __decorateClass([
1958
+ n({ type: Boolean, attribute: "is-preview", reflect: true })
1959
+ ], LuksoMarkdownEditor.prototype, "isPreview", 2);
1960
+ __decorateClass([
1961
+ n({ type: Number })
1962
+ ], LuksoMarkdownEditor.prototype, "rows", 2);
1963
+ __decorateClass([
1964
+ n({ type: String })
1965
+ ], LuksoMarkdownEditor.prototype, "placeholder", 2);
1966
+ __decorateClass([
1967
+ r()
1968
+ ], LuksoMarkdownEditor.prototype, "savedSelectionForPreview", 2);
1969
+ __decorateClass([
1970
+ r()
1971
+ ], LuksoMarkdownEditor.prototype, "isHeadingDropdownOpen", 2);
1972
+ __decorateClass([
1973
+ r()
1974
+ ], LuksoMarkdownEditor.prototype, "isColorDropdownOpen", 2);
1975
+ __decorateClass([
1976
+ r()
1977
+ ], LuksoMarkdownEditor.prototype, "isListDropdownOpen", 2);
1978
+ __decorateClass([
1979
+ r()
1980
+ ], LuksoMarkdownEditor.prototype, "currentSelection", 2);
1981
+ __decorateClass([
1982
+ r()
1983
+ ], LuksoMarkdownEditor.prototype, "savedSelection", 2);
1984
+ __decorateClass([
1985
+ r()
1986
+ ], LuksoMarkdownEditor.prototype, "activeFormats", 2);
1987
+ __decorateClass([
1988
+ e("lukso-textarea")
1989
+ ], LuksoMarkdownEditor.prototype, "textareaEl", 2);
1990
+ LuksoMarkdownEditor = __decorateClass([
1991
+ t("lukso-markdown-editor")
1992
+ ], LuksoMarkdownEditor);
1993
+
1994
+ export { LuksoMarkdownEditor };