@superleapai/flow-ui 2.3.6 → 2.3.7

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.
@@ -77,6 +77,40 @@
77
77
  /** Filled circle for "just color" mode (IconOrColor when only icon_color is set) */
78
78
  IconCircleFilled:
79
79
  '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" fill="currentColor"/></svg>',
80
+
81
+ // Rich text editor / toolbar (Tabler Icons outline, stroke 2)
82
+ IconBold:
83
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-bold"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 5h6a3.5 3.5 0 0 1 0 7h-6l0 -7" /><path d="M13 12h1a3.5 3.5 0 0 1 0 7h-7v-7" /></svg>',
84
+ IconItalic:
85
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-italic"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M11 5l6 0" /><path d="M7 19l6 0" /><path d="M14 5l-4 14" /></svg>',
86
+ IconUnderline:
87
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-underline"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 5v5a5 5 0 0 0 10 0v-5" /><path d="M5 19h14" /></svg>',
88
+ IconH1:
89
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-h-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19 18v-8l-2 2" /><path d="M4 6v12" /><path d="M12 6v12" /><path d="M11 18h2" /><path d="M3 18h2" /><path d="M4 12h8" /><path d="M3 6h2" /><path d="M11 6h2" /></svg>',
90
+ IconH2:
91
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-h-2"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M17 12a2 2 0 1 1 4 0c0 .591 -.417 1.318 -.816 1.858l-3.184 4.143l4 0" /><path d="M4 6v12" /><path d="M12 6v12" /><path d="M11 18h2" /><path d="M3 18h2" /><path d="M4 12h8" /><path d="M3 6h2" /><path d="M11 6h2" /></svg>',
92
+ IconH3:
93
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-h-3"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19 14a2 2 0 1 0 -2 -2" /><path d="M17 16a2 2 0 1 0 2 -2" /><path d="M4 6v12" /><path d="M12 6v12" /><path d="M11 18h2" /><path d="M3 18h2" /><path d="M4 12h8" /><path d="M3 6h2" /><path d="M11 6h2" /></svg>',
94
+ IconList:
95
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-list"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 6l11 0" /><path d="M9 12l11 0" /><path d="M9 18l11 0" /><path d="M5 6l0 .01" /><path d="M5 12l0 .01" /><path d="M5 18l0 .01" /></svg>',
96
+ IconListNumbers:
97
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-list-numbers"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M11 6h9" /><path d="M11 12h9" /><path d="M12 18h8" /><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4" /><path d="M6 10v-6l-2 2" /></svg>',
98
+ IconAlignLeft:
99
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-align-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6l16 0" /><path d="M4 12l10 0" /><path d="M4 18l14 0" /></svg>',
100
+ IconAlignCenter:
101
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-align-center"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6l16 0" /><path d="M8 12l8 0" /><path d="M6 18l12 0" /></svg>',
102
+ IconAlignRight:
103
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-align-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6l16 0" /><path d="M10 12l10 0" /><path d="M6 18l14 0" /></svg>',
104
+ IconCode:
105
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-code"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 8l-4 4l4 4" /><path d="M17 8l4 4l-4 4" /><path d="M14 4l-4 16" /></svg>',
106
+ IconLink:
107
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 15l6 -6" /><path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /><path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /></svg>',
108
+ IconPhoto:
109
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-photo"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 8h.01" /><path d="M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12" /><path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5" /><path d="M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3" /></svg>',
110
+ IconArrowBackUp:
111
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-back-up"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 14l-4 -4l4 -4" /><path d="M5 10h11a4 4 0 1 1 0 8h-1" /></svg>',
112
+ IconArrowForwardUp:
113
+ '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-forward-up"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 14l4 -4l-4 -4" /><path d="M19 10h-11a4 4 0 1 0 0 8h1" /></svg>',
80
114
  };
81
115
 
82
116
  function join() {
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Rich Text Editor Component (vanilla JS)
3
+ * Toolbar + contenteditable area with formatting (bold, italic, underline, headings, lists, alignment, link, image, code block, undo/redo).
4
+ * Styling matches design: rounded-12, toolbar bg-fill-tertiary-fill-light-gray, content area with min-height.
5
+ */
6
+
7
+ (function (global) {
8
+ "use strict";
9
+
10
+ var RICH_TEXT_CONTENT_STYLES =
11
+ ".rich-text-editor-content ul{list-style-type:disc;padding-left:1.5em;margin:0.5em 0}" +
12
+ ".rich-text-editor-content ol{list-style-type:decimal;padding-left:1.5em;margin:0.5em 0}" +
13
+ ".rich-text-editor-content li{margin:0.25em 0}" +
14
+ ".rich-text-editor-content li p{margin:0}" +
15
+ ".rich-text-editor-content h1{font-size:1.5rem;font-weight:700;line-height:1.3;margin:0.75em 0 0.5em}" +
16
+ ".rich-text-editor-content h2{font-size:1.25rem;font-weight:600;line-height:1.3;margin:0.75em 0 0.5em}" +
17
+ ".rich-text-editor-content h3{font-size:1.125rem;font-weight:600;line-height:1.3;margin:0.75em 0 0.5em}" +
18
+ ".rich-text-editor-content p{margin:0.5em 0}" +
19
+ ".rich-text-editor-content a{color:var(--color-primary-500);text-decoration:underline;cursor:pointer}" +
20
+ ".rich-text-editor-content pre{background:var(--color-neutral-100);border-radius:var(--sizes-size-8);padding:0.75em 1em;margin:0.5em 0;overflow-x:auto}" +
21
+ ".rich-text-editor-content code{font-family:ui-monospace,monospace;font-size:0.875em}" +
22
+ ".rich-text-editor-content img{max-width:100%;height:auto;margin:0.5em 0}" +
23
+ ".rich-text-editor-content blockquote{border-left:3px solid var(--color-neutral-200);padding-left:1em;margin:0.5em 0;color:var(--color-neutral-600)}" +
24
+ ".rich-text-editor-content hr{border:none;border-top:1px solid var(--color-neutral-150);margin:1em 0}" +
25
+ ".rich-text-editor-content .ProseMirror,.rich-text-editor-content [contenteditable]{outline:none}";
26
+
27
+ function join() {
28
+ return Array.prototype.filter.call(arguments, Boolean).join(" ");
29
+ }
30
+
31
+ function getDep(name) {
32
+ if (typeof global.FlowUI !== "undefined" && typeof global.FlowUI._getComponent === "function") {
33
+ var c = global.FlowUI._getComponent(name);
34
+ if (c) return c;
35
+ }
36
+ return global[name];
37
+ }
38
+
39
+ /** Get Tabler icon element (16px) from Icon component for toolbar. */
40
+ function getTablerIcon(iconName) {
41
+ var Icon = getDep("Icon");
42
+ if (!Icon || !Icon.iconMap || !Icon.iconMap[iconName]) return null;
43
+ var svgStr = Icon.iconMap[iconName];
44
+ var s16 = svgStr.replace(/width="24"/, 'width="16"').replace(/height="24"/, 'height="16"').replace(/width="20"/, 'width="16"').replace(/height="20"/, 'height="16"');
45
+ var span = document.createElement("span");
46
+ span.className = "flex items-center justify-center size-16";
47
+ span.innerHTML = s16;
48
+ return span;
49
+ }
50
+
51
+ function createToolbarButton(opts) {
52
+ var Button = getDep("Button");
53
+ if (!Button || typeof Button.create !== "function") {
54
+ throw new Error("RichTextEditor requires Button");
55
+ }
56
+ var icon = opts.iconStr ? getTablerIcon(opts.iconStr) : null;
57
+ return Button.create({
58
+ variant: opts.active ? "primary" : "outline",
59
+ size: "default",
60
+ title: opts.title,
61
+ icon: icon,
62
+ onClick: opts.onClick,
63
+ disabled: opts.disabled,
64
+ });
65
+ }
66
+
67
+ function createSeparator() {
68
+ var sep = document.createElement("div");
69
+ sep.className = "w-px h-16 bg-border-primary mx-4";
70
+ sep.setAttribute("aria-hidden", "true");
71
+ return sep;
72
+ }
73
+
74
+ /**
75
+ * Create a rich text editor
76
+ * @param {Object} config
77
+ * @param {string} [config.value] - Initial HTML content
78
+ * @param {string} [config.placeholder] - Placeholder when empty
79
+ * @param {number} [config.minHeightPx] - Min height of editor area (default 400)
80
+ * @param {boolean} [config.disabled]
81
+ * @param {Function} [config.onChange] - (html: string) => void
82
+ * @returns {HTMLElement} Wrapper element with getValue/setValue/setDisabled/getInput
83
+ */
84
+ function create(config) {
85
+ var value = config.value != null ? String(config.value) : "";
86
+ var placeholder = config.placeholder != null ? config.placeholder : "";
87
+ var minHeightPx = config.minHeightPx != null ? config.minHeightPx : 400;
88
+ var disabled = !!config.disabled;
89
+ var onChange = config.onChange;
90
+
91
+ var wrapper = document.createElement("div");
92
+ wrapper.className = "w-full rounded-12 border border-borderColor-border-primary shadow-soft-2x-small";
93
+
94
+ var styleEl = document.createElement("style");
95
+ styleEl.textContent = RICH_TEXT_CONTENT_STYLES;
96
+ wrapper.appendChild(styleEl);
97
+
98
+ var toolbar = document.createElement("div");
99
+ toolbar.className =
100
+ "flex flex-wrap gap-4 rounded-t-12 border-borderColor-border-primary bg-fill-tertiary-fill-light-gray p-6";
101
+ toolbar.setAttribute("role", "toolbar");
102
+
103
+ var editorEl = document.createElement("div");
104
+ editorEl.contentEditable = !disabled;
105
+ editorEl.className = join(
106
+ "rich-text-editor-content max-w-none rounded-b-12 border-t border-borderColor-border-primary p-8 text-reg-14 text-typography-primary-text"
107
+ );
108
+ editorEl.style.minHeight = minHeightPx + "px";
109
+ if (value) editorEl.innerHTML = value;
110
+ editorEl.setAttribute("data-placeholder", placeholder);
111
+
112
+ function isEmpty() {
113
+ var text = (editorEl.textContent || "").trim();
114
+ if (text) return false;
115
+ var html = (editorEl.innerHTML || "").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "");
116
+ return !html.trim();
117
+ }
118
+ function updatePlaceholder() {
119
+ if (placeholder && isEmpty()) {
120
+ editorEl.classList.add("empty");
121
+ editorEl.setAttribute("data-placeholder", placeholder);
122
+ } else {
123
+ editorEl.classList.remove("empty");
124
+ editorEl.removeAttribute("data-placeholder");
125
+ }
126
+ }
127
+ updatePlaceholder();
128
+
129
+ function getHtml() {
130
+ return editorEl.innerHTML;
131
+ }
132
+ function setHtml(html) {
133
+ editorEl.innerHTML = html || "";
134
+ updatePlaceholder();
135
+ }
136
+ function notifyChange() {
137
+ if (typeof onChange === "function") onChange(getHtml());
138
+ }
139
+
140
+ function isActive(cmd, val) {
141
+ try {
142
+ return document.queryCommandState(cmd);
143
+ } catch (e) {
144
+ return false;
145
+ }
146
+ }
147
+ function blockTag() {
148
+ var sel = window.getSelection();
149
+ if (!sel || sel.rangeCount === 0) return null;
150
+ var node = sel.anchorNode;
151
+ while (node && node !== editorEl) {
152
+ if (node.nodeType === 1) {
153
+ var n = node.nodeName.toLowerCase();
154
+ if (["h1", "h2", "h3", "p", "div", "pre", "blockquote"].indexOf(n) !== -1) return n;
155
+ }
156
+ node = node.parentNode;
157
+ }
158
+ return null;
159
+ }
160
+ function isAlignment(align) {
161
+ try {
162
+ if (align === "left") return document.queryCommandState("justifyLeft");
163
+ if (align === "center") return document.queryCommandState("justifyCenter");
164
+ if (align === "right") return document.queryCommandState("justifyRight");
165
+ } catch (e) {}
166
+ return false;
167
+ }
168
+
169
+ function refreshToolbar() {
170
+ toolbar.querySelectorAll("button").forEach(function (btn) {
171
+ var cmd = btn.getAttribute("data-command");
172
+ var val = btn.getAttribute("data-value");
173
+ if (!cmd) return;
174
+ var active = false;
175
+ if (cmd === "formatBlock") active = blockTag() === val;
176
+ else if (cmd === "justifyLeft" && val === "left") active = isAlignment("left");
177
+ else if (cmd === "justifyCenter" && val === "center") active = isAlignment("center");
178
+ else if (cmd === "justifyRight" && val === "right") active = isAlignment("right");
179
+ else active = isActive(cmd);
180
+ btn.classList.toggle("bg-primary-base", active);
181
+ btn.classList.toggle("border-primary-base", active);
182
+ btn.classList.toggle("text-typography-invert-text", active);
183
+ btn.classList.toggle("bg-fill-quarternary-fill-white", !active);
184
+ btn.classList.toggle("border-border-primary", !active);
185
+ btn.classList.toggle("text-typography-primary-text", !active);
186
+ });
187
+ }
188
+
189
+ function exec(cmd, value) {
190
+ editorEl.focus();
191
+ document.execCommand(cmd, false, value != null ? value : null);
192
+ refreshToolbar();
193
+ notifyChange();
194
+ }
195
+
196
+ function insertCodeBlock() {
197
+ editorEl.focus();
198
+ var sel = window.getSelection();
199
+ if (sel && sel.rangeCount) {
200
+ var range = sel.getRangeAt(0);
201
+ var pre = document.createElement("pre");
202
+ var code = document.createElement("code");
203
+ code.textContent = "code here";
204
+ pre.appendChild(code);
205
+ range.insertNode(pre);
206
+ range.setStart(code, 0);
207
+ range.setEnd(code, 0);
208
+ sel.removeAllRanges();
209
+ sel.addRange(range);
210
+ }
211
+ refreshToolbar();
212
+ notifyChange();
213
+ }
214
+
215
+ function addLink() {
216
+ var url = window.prompt("Enter the URL:", "https://");
217
+ if (url) {
218
+ exec("createLink", url);
219
+ }
220
+ }
221
+
222
+ function addImage() {
223
+ var input = document.createElement("input");
224
+ input.type = "file";
225
+ input.accept = "image/*";
226
+ input.onchange = function (e) {
227
+ var file = e.target && e.target.files && e.target.files[0];
228
+ if (file) {
229
+ var reader = new FileReader();
230
+ reader.onload = function (ev) {
231
+ var src = ev.target && ev.target.result;
232
+ if (src) {
233
+ editorEl.focus();
234
+ document.execCommand("insertImage", false, src);
235
+ notifyChange();
236
+ }
237
+ };
238
+ reader.readAsDataURL(file);
239
+ }
240
+ };
241
+ input.click();
242
+ }
243
+
244
+ function undo() {
245
+ editorEl.focus();
246
+ document.execCommand("undo", false, null);
247
+ refreshToolbar();
248
+ notifyChange();
249
+ }
250
+ function redo() {
251
+ editorEl.focus();
252
+ document.execCommand("redo", false, null);
253
+ refreshToolbar();
254
+ notifyChange();
255
+ }
256
+
257
+ function addBtn(iconStr, title, onClick, dataCommand, dataValue) {
258
+ var btn = createToolbarButton({
259
+ iconStr: iconStr,
260
+ title: title,
261
+ onClick: function () {
262
+ if (disabled) return;
263
+ onClick();
264
+ },
265
+ active: false,
266
+ });
267
+ if (dataCommand) btn.setAttribute("data-command", dataCommand);
268
+ if (dataValue != null) btn.setAttribute("data-value", dataValue);
269
+ toolbar.appendChild(btn);
270
+ }
271
+
272
+ addBtn("IconBold", "Bold (Ctrl+B)", function () { exec("bold"); }, "bold");
273
+ addBtn("IconItalic", "Italic (Ctrl+I)", function () { exec("italic"); }, "italic");
274
+ addBtn("IconUnderline", "Underline (Ctrl+U)", function () { exec("underline"); }, "underline");
275
+ toolbar.appendChild(createSeparator());
276
+ addBtn("IconH1", "Heading 1", function () { exec("formatBlock", "h1"); }, "formatBlock", "h1");
277
+ addBtn("IconH2", "Heading 2", function () { exec("formatBlock", "h2"); }, "formatBlock", "h2");
278
+ addBtn("IconH3", "Heading 3", function () { exec("formatBlock", "h3"); }, "formatBlock", "h3");
279
+ toolbar.appendChild(createSeparator());
280
+ addBtn("IconList", "Bullet List", function () { exec("insertUnorderedList"); }, "insertUnorderedList");
281
+ addBtn("IconListNumbers", "Ordered List", function () { exec("insertOrderedList"); }, "insertOrderedList");
282
+ toolbar.appendChild(createSeparator());
283
+ addBtn("IconAlignLeft", "Align Left", function () { exec("justifyLeft"); }, "justifyLeft", "left");
284
+ addBtn("IconAlignCenter", "Align Center", function () { exec("justifyCenter"); }, "justifyCenter", "center");
285
+ addBtn("IconAlignRight", "Align Right", function () { exec("justifyRight"); }, "justifyRight", "right");
286
+ toolbar.appendChild(createSeparator());
287
+ addBtn("IconCode", "Code Block", insertCodeBlock, "formatBlock", "pre");
288
+ addBtn("IconLink", "Add Link", addLink, "link");
289
+ addBtn("IconPhoto", "Insert Image", addImage);
290
+ toolbar.appendChild(createSeparator());
291
+ addBtn("IconArrowBackUp", "Undo", undo);
292
+ addBtn("IconArrowForwardUp", "Redo", redo);
293
+
294
+ editorEl.addEventListener("input", function () {
295
+ updatePlaceholder();
296
+ refreshToolbar();
297
+ notifyChange();
298
+ });
299
+ editorEl.addEventListener("keyup", refreshToolbar);
300
+ editorEl.addEventListener("mouseup", refreshToolbar);
301
+ editorEl.addEventListener("focus", refreshToolbar);
302
+
303
+ if (placeholder) {
304
+ var placeholderStyles = document.createElement("style");
305
+ placeholderStyles.textContent =
306
+ ".rich-text-editor-content.empty:before{content:attr(data-placeholder);color:var(--color-neutral-400);pointer-events:none}";
307
+ wrapper.appendChild(placeholderStyles);
308
+ }
309
+
310
+ wrapper.appendChild(toolbar);
311
+ wrapper.appendChild(editorEl);
312
+
313
+ wrapper.getInput = function () {
314
+ return editorEl;
315
+ };
316
+ wrapper.getValue = function () {
317
+ return getHtml();
318
+ };
319
+ wrapper.setValue = function (v) {
320
+ setHtml(v);
321
+ };
322
+ wrapper.setDisabled = function (d) {
323
+ disabled = !!d;
324
+ editorEl.contentEditable = !disabled;
325
+ toolbar.querySelectorAll("button").forEach(function (b) {
326
+ b.disabled = disabled;
327
+ });
328
+ };
329
+
330
+ return wrapper;
331
+ }
332
+
333
+ global.RichTextEditorComponent = {
334
+ create: create,
335
+ };
336
+ })(typeof window !== "undefined" ? window : this);
package/core/flow.js CHANGED
@@ -248,6 +248,48 @@
248
248
  return field;
249
249
  }
250
250
 
251
+ /**
252
+ * Create a rich text editor field
253
+ * @param {Object} config - Configuration object
254
+ * @param {string} config.label - Field label
255
+ * @param {string} config.fieldId - State key for this field
256
+ * @param {string} [config.placeholder] - Placeholder when empty
257
+ * @param {boolean} [config.required] - Whether field is required
258
+ * @param {string} [config.helpText] - Optional help text for tooltip
259
+ * @param {number} [config.minHeightPx] - Min height of editor area in pixels (default 400)
260
+ * @param {boolean} [config.disabled] - Whether editor is disabled
261
+ * @returns {HTMLElement} Field element
262
+ */
263
+ function createRichTextEditor(config) {
264
+ const { label, fieldId, placeholder, required = false, helpText = null, minHeightPx = 400, disabled = false } = config;
265
+
266
+ const field = createFieldWrapper(label, required, helpText);
267
+ field.setAttribute("data-field-id", fieldId);
268
+
269
+ if (getComponent("RichTextEditorComponent") && getComponent("RichTextEditorComponent").create) {
270
+ const currentValue = get(fieldId) || "";
271
+ const editorEl = getComponent("RichTextEditorComponent").create({
272
+ value: currentValue,
273
+ placeholder: placeholder || "",
274
+ minHeightPx,
275
+ disabled,
276
+ onChange: (html) => set(fieldId, html),
277
+ });
278
+ editorEl._fieldId = fieldId;
279
+ field.appendChild(editorEl);
280
+ return field;
281
+ }
282
+
283
+ const fallback = document.createElement("textarea");
284
+ fallback.className = "textarea min-h-[400px]";
285
+ fallback.placeholder = placeholder || `Enter ${label.toLowerCase()}`;
286
+ fallback.value = get(fieldId) || "";
287
+ fallback.disabled = disabled;
288
+ fallback.addEventListener("change", (e) => set(fieldId, e.target.value));
289
+ field.appendChild(fallback);
290
+ return field;
291
+ }
292
+
251
293
  /**
252
294
  * Create a select dropdown field (using custom select component)
253
295
  * @param {Object} config - Configuration object
@@ -1658,6 +1700,7 @@
1658
1700
  // Form components
1659
1701
  createInput,
1660
1702
  createTextarea,
1703
+ createRichTextEditor,
1661
1704
  createSelect,
1662
1705
  createTimePicker,
1663
1706
  createDateTimePicker,