@fragments-sdk/ui 0.9.7 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -24
- package/dist/assets/ui.css +304 -0
- package/dist/blocks/BlogEditor.block.d.ts +3 -0
- package/dist/blocks/BlogEditor.block.d.ts.map +1 -0
- package/dist/components/Editor/Editor.module.scss.cjs +57 -0
- package/dist/components/Editor/Editor.module.scss.cjs.map +1 -0
- package/dist/components/Editor/Editor.module.scss.js +57 -0
- package/dist/components/Editor/Editor.module.scss.js.map +1 -0
- package/dist/components/Editor/index.cjs +548 -0
- package/dist/components/Editor/index.cjs.map +1 -0
- package/dist/components/Editor/index.d.ts +107 -0
- package/dist/components/Editor/index.d.ts.map +1 -0
- package/dist/components/Editor/index.js +531 -0
- package/dist/components/Editor/index.js.map +1 -0
- package/dist/components/Sidebar/index.cjs +6 -11
- package/dist/components/Sidebar/index.cjs.map +1 -1
- package/dist/components/Sidebar/index.d.ts.map +1 -1
- package/dist/components/Sidebar/index.js +6 -11
- package/dist/components/Sidebar/index.js.map +1 -1
- package/dist/components/Theme/index.cjs +86 -1
- package/dist/components/Theme/index.cjs.map +1 -1
- package/dist/components/Theme/index.d.ts +44 -1
- package/dist/components/Theme/index.d.ts.map +1 -1
- package/dist/components/Theme/index.js +86 -1
- package/dist/components/Theme/index.js.map +1 -1
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/keyboard-shortcuts.cjs +295 -0
- package/dist/utils/keyboard-shortcuts.cjs.map +1 -0
- package/dist/utils/keyboard-shortcuts.d.ts +293 -0
- package/dist/utils/keyboard-shortcuts.d.ts.map +1 -0
- package/dist/utils/keyboard-shortcuts.js +295 -0
- package/dist/utils/keyboard-shortcuts.js.map +1 -0
- package/fragments.json +1 -1
- package/package.json +32 -3
- package/src/blocks/BlogEditor.block.ts +34 -0
- package/src/components/Editor/Editor.fragment.tsx +322 -0
- package/src/components/Editor/Editor.module.scss +333 -0
- package/src/components/Editor/Editor.test.tsx +174 -0
- package/src/components/Editor/index.tsx +815 -0
- package/src/components/Sidebar/index.tsx +7 -14
- package/src/components/Theme/index.tsx +168 -1
- package/src/index.ts +49 -0
- package/src/tokens/_seeds.scss +20 -0
- package/src/utils/keyboard-shortcuts.test.ts +357 -0
- package/src/utils/keyboard-shortcuts.ts +502 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const React = require("react");
|
|
5
|
+
const Editor_module = require("./Editor.module.scss.cjs");
|
|
6
|
+
const react = require("@phosphor-icons/react");
|
|
7
|
+
const keyboardShortcuts = require("../../utils/keyboard-shortcuts.cjs");
|
|
8
|
+
function _interopNamespaceDefault(e) {
|
|
9
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
10
|
+
if (e) {
|
|
11
|
+
for (const k in e) {
|
|
12
|
+
if (k !== "default") {
|
|
13
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
14
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
get: () => e[k]
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
n.default = e;
|
|
22
|
+
return Object.freeze(n);
|
|
23
|
+
}
|
|
24
|
+
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
|
|
25
|
+
let _useEditor = null;
|
|
26
|
+
let _EditorContent = null;
|
|
27
|
+
let _StarterKit = null;
|
|
28
|
+
let _LinkExtension = null;
|
|
29
|
+
let _tiptapLoaded = false;
|
|
30
|
+
let _tiptapFailed = false;
|
|
31
|
+
function loadTipTapDeps() {
|
|
32
|
+
if (_tiptapLoaded) return;
|
|
33
|
+
_tiptapLoaded = true;
|
|
34
|
+
try {
|
|
35
|
+
const tiptapReact = require("@tiptap/react");
|
|
36
|
+
const starterKit = require("@tiptap/starter-kit");
|
|
37
|
+
const linkExt = require("@tiptap/extension-link");
|
|
38
|
+
_useEditor = tiptapReact.useEditor;
|
|
39
|
+
_EditorContent = tiptapReact.EditorContent;
|
|
40
|
+
_StarterKit = starterKit.default ?? starterKit.StarterKit ?? starterKit;
|
|
41
|
+
_LinkExtension = linkExt.default ?? linkExt.Link ?? linkExt;
|
|
42
|
+
} catch {
|
|
43
|
+
_tiptapFailed = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const FORMAT_META = {
|
|
47
|
+
bold: { icon: react.TextB, label: "Bold", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_BOLD.label },
|
|
48
|
+
italic: { icon: react.TextItalic, label: "Italic", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_ITALIC.label },
|
|
49
|
+
strikethrough: { icon: react.TextStrikethrough, label: "Strikethrough", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_STRIKETHROUGH.label },
|
|
50
|
+
link: { icon: react.LinkSimple, label: "Link", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_LINK.label },
|
|
51
|
+
code: { icon: react.Code, label: "Code", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_CODE.label },
|
|
52
|
+
bulletList: { icon: react.ListBullets, label: "Bullet list", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_BULLET_LIST.label },
|
|
53
|
+
orderedList: { icon: react.ListNumbers, label: "Ordered list", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_ORDERED_LIST.label },
|
|
54
|
+
heading1: { icon: react.TextHOne, label: "Heading 1", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_HEADING1.label },
|
|
55
|
+
heading2: { icon: react.TextHTwo, label: "Heading 2", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_HEADING2.label },
|
|
56
|
+
heading3: { icon: react.TextHThree, label: "Heading 3", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_HEADING3.label },
|
|
57
|
+
blockquote: { icon: react.Quotes, label: "Blockquote", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_BLOCKQUOTE.label },
|
|
58
|
+
undo: { icon: react.ArrowCounterClockwise, label: "Undo", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_UNDO.label },
|
|
59
|
+
redo: { icon: react.ArrowClockwise, label: "Redo", shortcut: keyboardShortcuts.KEYBOARD_SHORTCUTS.EDITOR_REDO.label }
|
|
60
|
+
};
|
|
61
|
+
const DEFAULT_FORMATS = ["bold", "italic", "strikethrough", "link", "code", "bulletList"];
|
|
62
|
+
const ACTION_FORMATS = /* @__PURE__ */ new Set(["undo", "redo"]);
|
|
63
|
+
const DEFAULT_STATUS_LABELS = {
|
|
64
|
+
idle: "",
|
|
65
|
+
saving: "SAVING...",
|
|
66
|
+
saved: "AUTO-SAVED",
|
|
67
|
+
error: "SAVE FAILED"
|
|
68
|
+
};
|
|
69
|
+
function getSelection(textarea) {
|
|
70
|
+
return {
|
|
71
|
+
start: textarea.selectionStart,
|
|
72
|
+
end: textarea.selectionEnd,
|
|
73
|
+
text: textarea.value.substring(textarea.selectionStart, textarea.selectionEnd)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function wrapSelection(textarea, prefix, suffix, setValue) {
|
|
77
|
+
const sel = getSelection(textarea);
|
|
78
|
+
const before = textarea.value.substring(0, sel.start);
|
|
79
|
+
const after = textarea.value.substring(sel.end);
|
|
80
|
+
const wrapped = `${prefix}${sel.text || "text"}${suffix}`;
|
|
81
|
+
const newValue = `${before}${wrapped}${after}`;
|
|
82
|
+
setValue(newValue);
|
|
83
|
+
requestAnimationFrame(() => {
|
|
84
|
+
textarea.focus();
|
|
85
|
+
const newStart = sel.start + prefix.length;
|
|
86
|
+
const newEnd = newStart + (sel.text || "text").length;
|
|
87
|
+
textarea.setSelectionRange(newStart, newEnd);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function applyMarkdownFormat(format, textarea, setValue) {
|
|
91
|
+
switch (format) {
|
|
92
|
+
case "bold":
|
|
93
|
+
wrapSelection(textarea, "**", "**", setValue);
|
|
94
|
+
break;
|
|
95
|
+
case "italic":
|
|
96
|
+
wrapSelection(textarea, "*", "*", setValue);
|
|
97
|
+
break;
|
|
98
|
+
case "strikethrough":
|
|
99
|
+
wrapSelection(textarea, "~~", "~~", setValue);
|
|
100
|
+
break;
|
|
101
|
+
case "code":
|
|
102
|
+
wrapSelection(textarea, "`", "`", setValue);
|
|
103
|
+
break;
|
|
104
|
+
case "link": {
|
|
105
|
+
const sel = getSelection(textarea);
|
|
106
|
+
const linkText = sel.text || "link text";
|
|
107
|
+
const before = textarea.value.substring(0, sel.start);
|
|
108
|
+
const after = textarea.value.substring(sel.end);
|
|
109
|
+
const newValue = `${before}[${linkText}](url)${after}`;
|
|
110
|
+
setValue(newValue);
|
|
111
|
+
requestAnimationFrame(() => {
|
|
112
|
+
textarea.focus();
|
|
113
|
+
const urlStart = sel.start + linkText.length + 3;
|
|
114
|
+
textarea.setSelectionRange(urlStart, urlStart + 3);
|
|
115
|
+
});
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "bulletList": {
|
|
119
|
+
const sel = getSelection(textarea);
|
|
120
|
+
const before = textarea.value.substring(0, sel.start);
|
|
121
|
+
const after = textarea.value.substring(sel.end);
|
|
122
|
+
const lines = (sel.text || "item").split("\n");
|
|
123
|
+
const bulleted = lines.map((l) => `- ${l}`).join("\n");
|
|
124
|
+
const newValue = `${before}${bulleted}${after}`;
|
|
125
|
+
setValue(newValue);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "orderedList": {
|
|
129
|
+
const sel = getSelection(textarea);
|
|
130
|
+
const before = textarea.value.substring(0, sel.start);
|
|
131
|
+
const after = textarea.value.substring(sel.end);
|
|
132
|
+
const lines = (sel.text || "item").split("\n");
|
|
133
|
+
const numbered = lines.map((l, i) => `${i + 1}. ${l}`).join("\n");
|
|
134
|
+
const newValue = `${before}${numbered}${after}`;
|
|
135
|
+
setValue(newValue);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case "heading1":
|
|
139
|
+
wrapSelection(textarea, "# ", "", setValue);
|
|
140
|
+
break;
|
|
141
|
+
case "heading2":
|
|
142
|
+
wrapSelection(textarea, "## ", "", setValue);
|
|
143
|
+
break;
|
|
144
|
+
case "heading3":
|
|
145
|
+
wrapSelection(textarea, "### ", "", setValue);
|
|
146
|
+
break;
|
|
147
|
+
case "blockquote": {
|
|
148
|
+
const sel = getSelection(textarea);
|
|
149
|
+
const before = textarea.value.substring(0, sel.start);
|
|
150
|
+
const after = textarea.value.substring(sel.end);
|
|
151
|
+
const lines = (sel.text || "quote").split("\n");
|
|
152
|
+
const quoted = lines.map((l) => `> ${l}`).join("\n");
|
|
153
|
+
const newValue = `${before}${quoted}${after}`;
|
|
154
|
+
setValue(newValue);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const EditorContext = React__namespace.createContext(null);
|
|
160
|
+
function useEditorContext() {
|
|
161
|
+
const context = React__namespace.useContext(EditorContext);
|
|
162
|
+
if (!context) {
|
|
163
|
+
throw new Error("Editor compound components must be used within an Editor");
|
|
164
|
+
}
|
|
165
|
+
return context;
|
|
166
|
+
}
|
|
167
|
+
function useControllableState(controlledValue, defaultValue, onChange) {
|
|
168
|
+
const [uncontrolledValue, setUncontrolledValue] = React__namespace.useState(defaultValue);
|
|
169
|
+
const isControlled = controlledValue !== void 0;
|
|
170
|
+
const value = isControlled ? controlledValue : uncontrolledValue;
|
|
171
|
+
const setValue = React__namespace.useCallback(
|
|
172
|
+
(newValue) => {
|
|
173
|
+
if (!isControlled) {
|
|
174
|
+
setUncontrolledValue(newValue);
|
|
175
|
+
}
|
|
176
|
+
onChange == null ? void 0 : onChange(newValue);
|
|
177
|
+
},
|
|
178
|
+
[isControlled, onChange]
|
|
179
|
+
);
|
|
180
|
+
return [value, setValue];
|
|
181
|
+
}
|
|
182
|
+
function countWords(text) {
|
|
183
|
+
const trimmed = text.trim();
|
|
184
|
+
if (!trimmed) return 0;
|
|
185
|
+
return trimmed.split(/\s+/).length;
|
|
186
|
+
}
|
|
187
|
+
function EditorRoot({
|
|
188
|
+
children,
|
|
189
|
+
value: controlledValue,
|
|
190
|
+
defaultValue = "",
|
|
191
|
+
onValueChange,
|
|
192
|
+
placeholder = "Start typing...",
|
|
193
|
+
disabled = false,
|
|
194
|
+
readOnly = false,
|
|
195
|
+
formats = DEFAULT_FORMATS,
|
|
196
|
+
toolbar = true,
|
|
197
|
+
statusBar = true,
|
|
198
|
+
onAutoSave,
|
|
199
|
+
autoSaveInterval = 3e4,
|
|
200
|
+
size = "md",
|
|
201
|
+
maxLength,
|
|
202
|
+
className,
|
|
203
|
+
...htmlProps
|
|
204
|
+
}) {
|
|
205
|
+
const contentRef = React__namespace.useRef(null);
|
|
206
|
+
const [value, setValue] = useControllableState(
|
|
207
|
+
controlledValue,
|
|
208
|
+
defaultValue,
|
|
209
|
+
onValueChange
|
|
210
|
+
);
|
|
211
|
+
const [saveStatus, setSaveStatus] = React__namespace.useState("idle");
|
|
212
|
+
loadTipTapDeps();
|
|
213
|
+
const hasTipTap = !_tiptapFailed && _useEditor && _EditorContent && _StarterKit;
|
|
214
|
+
const mode = hasTipTap ? "rich" : "markdown";
|
|
215
|
+
const tiptapEditor = hasTipTap ? (
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
217
|
+
_useEditor({
|
|
218
|
+
extensions: [
|
|
219
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
220
|
+
_StarterKit.configure({
|
|
221
|
+
heading: { levels: [1, 2, 3] },
|
|
222
|
+
blockquote: {},
|
|
223
|
+
codeBlock: false,
|
|
224
|
+
horizontalRule: false,
|
|
225
|
+
hardBreak: false
|
|
226
|
+
}),
|
|
227
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
228
|
+
_LinkExtension.configure({
|
|
229
|
+
openOnClick: false,
|
|
230
|
+
HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" }
|
|
231
|
+
})
|
|
232
|
+
],
|
|
233
|
+
editorProps: {
|
|
234
|
+
attributes: {
|
|
235
|
+
role: "textbox",
|
|
236
|
+
"aria-label": placeholder,
|
|
237
|
+
"aria-multiline": "true"
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
content: defaultValue || controlledValue || "",
|
|
241
|
+
editable: !disabled && !readOnly,
|
|
242
|
+
onUpdate: ({ editor: e }) => {
|
|
243
|
+
const html = e.getHTML();
|
|
244
|
+
setValue(html);
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
) : null;
|
|
248
|
+
React__namespace.useEffect(() => {
|
|
249
|
+
if (tiptapEditor && controlledValue !== void 0) {
|
|
250
|
+
const currentContent = tiptapEditor.getHTML();
|
|
251
|
+
if (currentContent !== controlledValue) {
|
|
252
|
+
tiptapEditor.commands.setContent(controlledValue, false);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}, [controlledValue, tiptapEditor]);
|
|
256
|
+
React__namespace.useEffect(() => {
|
|
257
|
+
if (tiptapEditor) {
|
|
258
|
+
tiptapEditor.setEditable(!disabled && !readOnly);
|
|
259
|
+
}
|
|
260
|
+
}, [tiptapEditor, disabled, readOnly]);
|
|
261
|
+
React__namespace.useEffect(() => {
|
|
262
|
+
if (!onAutoSave || !value) return;
|
|
263
|
+
const timer = setTimeout(() => {
|
|
264
|
+
setSaveStatus("saving");
|
|
265
|
+
try {
|
|
266
|
+
onAutoSave(value);
|
|
267
|
+
setSaveStatus("saved");
|
|
268
|
+
} catch {
|
|
269
|
+
setSaveStatus("error");
|
|
270
|
+
}
|
|
271
|
+
}, autoSaveInterval);
|
|
272
|
+
return () => clearTimeout(timer);
|
|
273
|
+
}, [value, onAutoSave, autoSaveInterval]);
|
|
274
|
+
const toggleFormat = React__namespace.useCallback(
|
|
275
|
+
(format) => {
|
|
276
|
+
if (disabled || readOnly) return;
|
|
277
|
+
if (tiptapEditor) {
|
|
278
|
+
switch (format) {
|
|
279
|
+
case "bold":
|
|
280
|
+
tiptapEditor.chain().focus().toggleBold().run();
|
|
281
|
+
break;
|
|
282
|
+
case "italic":
|
|
283
|
+
tiptapEditor.chain().focus().toggleItalic().run();
|
|
284
|
+
break;
|
|
285
|
+
case "strikethrough":
|
|
286
|
+
tiptapEditor.chain().focus().toggleStrike().run();
|
|
287
|
+
break;
|
|
288
|
+
case "code":
|
|
289
|
+
tiptapEditor.chain().focus().toggleCode().run();
|
|
290
|
+
break;
|
|
291
|
+
case "bulletList":
|
|
292
|
+
tiptapEditor.chain().focus().toggleBulletList().run();
|
|
293
|
+
break;
|
|
294
|
+
case "orderedList":
|
|
295
|
+
tiptapEditor.chain().focus().toggleOrderedList().run();
|
|
296
|
+
break;
|
|
297
|
+
case "heading1":
|
|
298
|
+
tiptapEditor.chain().focus().toggleHeading({ level: 1 }).run();
|
|
299
|
+
break;
|
|
300
|
+
case "heading2":
|
|
301
|
+
tiptapEditor.chain().focus().toggleHeading({ level: 2 }).run();
|
|
302
|
+
break;
|
|
303
|
+
case "heading3":
|
|
304
|
+
tiptapEditor.chain().focus().toggleHeading({ level: 3 }).run();
|
|
305
|
+
break;
|
|
306
|
+
case "blockquote":
|
|
307
|
+
tiptapEditor.chain().focus().toggleBlockquote().run();
|
|
308
|
+
break;
|
|
309
|
+
case "undo":
|
|
310
|
+
tiptapEditor.chain().focus().undo().run();
|
|
311
|
+
break;
|
|
312
|
+
case "redo":
|
|
313
|
+
tiptapEditor.chain().focus().redo().run();
|
|
314
|
+
break;
|
|
315
|
+
case "link": {
|
|
316
|
+
const previousUrl = tiptapEditor.getAttributes("link").href;
|
|
317
|
+
if (previousUrl) {
|
|
318
|
+
tiptapEditor.chain().focus().unsetLink().run();
|
|
319
|
+
} else {
|
|
320
|
+
const url = window.prompt("Enter URL");
|
|
321
|
+
if (url) {
|
|
322
|
+
tiptapEditor.chain().focus().setLink({ href: url }).run();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
} else if (contentRef.current) {
|
|
329
|
+
applyMarkdownFormat(format, contentRef.current, setValue);
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
[disabled, readOnly, tiptapEditor, setValue]
|
|
333
|
+
);
|
|
334
|
+
const isFormatActive = React__namespace.useCallback(
|
|
335
|
+
(format) => {
|
|
336
|
+
if (!tiptapEditor) return false;
|
|
337
|
+
switch (format) {
|
|
338
|
+
case "bold":
|
|
339
|
+
return tiptapEditor.isActive("bold");
|
|
340
|
+
case "italic":
|
|
341
|
+
return tiptapEditor.isActive("italic");
|
|
342
|
+
case "strikethrough":
|
|
343
|
+
return tiptapEditor.isActive("strike");
|
|
344
|
+
case "code":
|
|
345
|
+
return tiptapEditor.isActive("code");
|
|
346
|
+
case "bulletList":
|
|
347
|
+
return tiptapEditor.isActive("bulletList");
|
|
348
|
+
case "orderedList":
|
|
349
|
+
return tiptapEditor.isActive("orderedList");
|
|
350
|
+
case "heading1":
|
|
351
|
+
return tiptapEditor.isActive("heading", { level: 1 });
|
|
352
|
+
case "heading2":
|
|
353
|
+
return tiptapEditor.isActive("heading", { level: 2 });
|
|
354
|
+
case "heading3":
|
|
355
|
+
return tiptapEditor.isActive("heading", { level: 3 });
|
|
356
|
+
case "blockquote":
|
|
357
|
+
return tiptapEditor.isActive("blockquote");
|
|
358
|
+
case "link":
|
|
359
|
+
return tiptapEditor.isActive("link");
|
|
360
|
+
case "undo":
|
|
361
|
+
case "redo":
|
|
362
|
+
return false;
|
|
363
|
+
// Actions don't have active state
|
|
364
|
+
default:
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
[tiptapEditor]
|
|
369
|
+
);
|
|
370
|
+
const wordCount = React__namespace.useMemo(() => {
|
|
371
|
+
var _a;
|
|
372
|
+
if (tiptapEditor) {
|
|
373
|
+
const text = ((_a = tiptapEditor.getText) == null ? void 0 : _a.call(tiptapEditor)) ?? "";
|
|
374
|
+
return countWords(text);
|
|
375
|
+
}
|
|
376
|
+
return countWords(value);
|
|
377
|
+
}, [value, tiptapEditor]);
|
|
378
|
+
const charCount = React__namespace.useMemo(() => {
|
|
379
|
+
var _a;
|
|
380
|
+
if (tiptapEditor) {
|
|
381
|
+
return (((_a = tiptapEditor.getText) == null ? void 0 : _a.call(tiptapEditor)) ?? "").length;
|
|
382
|
+
}
|
|
383
|
+
return value.length;
|
|
384
|
+
}, [value, tiptapEditor]);
|
|
385
|
+
const contextValue = {
|
|
386
|
+
value,
|
|
387
|
+
setValue,
|
|
388
|
+
placeholder,
|
|
389
|
+
disabled,
|
|
390
|
+
readOnly,
|
|
391
|
+
formats,
|
|
392
|
+
editor: tiptapEditor,
|
|
393
|
+
mode,
|
|
394
|
+
size,
|
|
395
|
+
maxLength,
|
|
396
|
+
wordCount,
|
|
397
|
+
charCount,
|
|
398
|
+
toggleFormat,
|
|
399
|
+
isFormatActive,
|
|
400
|
+
saveStatus,
|
|
401
|
+
contentRef
|
|
402
|
+
};
|
|
403
|
+
const classes = [
|
|
404
|
+
Editor_module.default.editor,
|
|
405
|
+
disabled && Editor_module.default.disabled,
|
|
406
|
+
readOnly && Editor_module.default.readOnly,
|
|
407
|
+
className
|
|
408
|
+
].filter(Boolean).join(" ");
|
|
409
|
+
const hasCustomChildren = children !== void 0;
|
|
410
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EditorContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
411
|
+
"div",
|
|
412
|
+
{
|
|
413
|
+
...htmlProps,
|
|
414
|
+
className: classes,
|
|
415
|
+
"data-disabled": disabled || void 0,
|
|
416
|
+
"data-readonly": readOnly || void 0,
|
|
417
|
+
"data-size": size,
|
|
418
|
+
children: hasCustomChildren ? children : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
419
|
+
toolbar && /* @__PURE__ */ jsxRuntime.jsx(EditorToolbar, { children: /* @__PURE__ */ jsxRuntime.jsx(EditorToolbarGroup, { "aria-label": "Text formatting", children: formats.map((f) => /* @__PURE__ */ jsxRuntime.jsx(EditorToolbarButton, { format: f }, f)) }) }),
|
|
420
|
+
/* @__PURE__ */ jsxRuntime.jsx(EditorContentArea, {}),
|
|
421
|
+
statusBar && /* @__PURE__ */ jsxRuntime.jsx(EditorStatusBar, { showWordCount: true, showCharCount: true })
|
|
422
|
+
] })
|
|
423
|
+
}
|
|
424
|
+
) });
|
|
425
|
+
}
|
|
426
|
+
function EditorToolbar({ children, className }) {
|
|
427
|
+
const classes = [Editor_module.default.toolbar, className].filter(Boolean).join(" ");
|
|
428
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes, role: "toolbar", "aria-label": "Editor formatting", children });
|
|
429
|
+
}
|
|
430
|
+
function EditorToolbarGroup({ children, "aria-label": ariaLabel, className }) {
|
|
431
|
+
const classes = [Editor_module.default.toolbarGroup, className].filter(Boolean).join(" ");
|
|
432
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes, role: "group", "aria-label": ariaLabel, children });
|
|
433
|
+
}
|
|
434
|
+
function EditorToolbarButton({ format, className }) {
|
|
435
|
+
const { toggleFormat, isFormatActive, disabled, readOnly, editor, mode } = useEditorContext();
|
|
436
|
+
const meta = FORMAT_META[format];
|
|
437
|
+
const isAction = ACTION_FORMATS.has(format);
|
|
438
|
+
const active = isAction ? false : isFormatActive(format);
|
|
439
|
+
const IconComponent = meta.icon;
|
|
440
|
+
let isDisabled = disabled || readOnly;
|
|
441
|
+
if (isAction && !isDisabled) {
|
|
442
|
+
if (mode === "markdown") {
|
|
443
|
+
isDisabled = true;
|
|
444
|
+
} else if (editor) {
|
|
445
|
+
isDisabled = format === "undo" ? !editor.can().undo() : !editor.can().redo();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const classes = [
|
|
449
|
+
Editor_module.default.toolbarButton,
|
|
450
|
+
active && Editor_module.default.toolbarButtonActive,
|
|
451
|
+
className
|
|
452
|
+
].filter(Boolean).join(" ");
|
|
453
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
454
|
+
"button",
|
|
455
|
+
{
|
|
456
|
+
type: "button",
|
|
457
|
+
className: classes,
|
|
458
|
+
onClick: () => toggleFormat(format),
|
|
459
|
+
disabled: isDisabled,
|
|
460
|
+
"aria-label": meta.label,
|
|
461
|
+
title: `${meta.label} (${meta.shortcut})`,
|
|
462
|
+
...isAction ? {} : { "aria-pressed": active },
|
|
463
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(IconComponent, { size: 16, weight: active ? "bold" : "regular" })
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
function EditorSeparator({ className }) {
|
|
468
|
+
const classes = [Editor_module.default.separator, className].filter(Boolean).join(" ");
|
|
469
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes, role: "separator", "aria-orientation": "vertical" });
|
|
470
|
+
}
|
|
471
|
+
function EditorStatusIndicator({ status: statusOverride, labels, className }) {
|
|
472
|
+
const { saveStatus } = useEditorContext();
|
|
473
|
+
const status = statusOverride ?? saveStatus;
|
|
474
|
+
const mergedLabels = { ...DEFAULT_STATUS_LABELS, ...labels };
|
|
475
|
+
const label = mergedLabels[status];
|
|
476
|
+
if (!label) return null;
|
|
477
|
+
const classes = [
|
|
478
|
+
Editor_module.default.statusIndicator,
|
|
479
|
+
status === "error" && Editor_module.default.statusError,
|
|
480
|
+
className
|
|
481
|
+
].filter(Boolean).join(" ");
|
|
482
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: classes, "aria-live": "polite", role: "status", children: label });
|
|
483
|
+
}
|
|
484
|
+
function EditorContentArea({ className }) {
|
|
485
|
+
const { value, setValue, placeholder, disabled, readOnly, editor, mode, contentRef } = useEditorContext();
|
|
486
|
+
if (mode === "rich" && editor && _EditorContent) {
|
|
487
|
+
const TipTapContent = _EditorContent;
|
|
488
|
+
const classes2 = [Editor_module.default.content, Editor_module.default.contentRich, className].filter(Boolean).join(" ");
|
|
489
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes2, "data-placeholder": placeholder, children: /* @__PURE__ */ jsxRuntime.jsx(TipTapContent, { editor }) });
|
|
490
|
+
}
|
|
491
|
+
const classes = [Editor_module.default.content, className].filter(Boolean).join(" ");
|
|
492
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
493
|
+
"textarea",
|
|
494
|
+
{
|
|
495
|
+
ref: contentRef,
|
|
496
|
+
className: Editor_module.default.contentTextarea,
|
|
497
|
+
value,
|
|
498
|
+
onChange: (e) => setValue(e.target.value),
|
|
499
|
+
placeholder,
|
|
500
|
+
disabled,
|
|
501
|
+
readOnly,
|
|
502
|
+
"aria-label": placeholder
|
|
503
|
+
}
|
|
504
|
+
) });
|
|
505
|
+
}
|
|
506
|
+
function EditorStatusBar({ showWordCount = true, showCharCount = true, className }) {
|
|
507
|
+
const { wordCount, charCount, maxLength } = useEditorContext();
|
|
508
|
+
const classes = [Editor_module.default.statusBar, className].filter(Boolean).join(" ");
|
|
509
|
+
const isOverLimit = maxLength !== void 0 && charCount > maxLength;
|
|
510
|
+
const isNearLimit = maxLength !== void 0 && !isOverLimit && charCount >= maxLength * 0.9;
|
|
511
|
+
const charLimitClasses = [
|
|
512
|
+
Editor_module.default.statusBarItem,
|
|
513
|
+
isNearLimit && Editor_module.default.statusBarItemWarning,
|
|
514
|
+
isOverLimit && Editor_module.default.statusBarItemError
|
|
515
|
+
].filter(Boolean).join(" ");
|
|
516
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes, "aria-label": "Editor statistics", children: [
|
|
517
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: Editor_module.default.statusBarLeft }),
|
|
518
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: Editor_module.default.statusBarRight, children: [
|
|
519
|
+
showWordCount && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: Editor_module.default.statusBarItem, children: [
|
|
520
|
+
wordCount,
|
|
521
|
+
" ",
|
|
522
|
+
wordCount === 1 ? "Word" : "Words"
|
|
523
|
+
] }),
|
|
524
|
+
showWordCount && showCharCount && /* @__PURE__ */ jsxRuntime.jsx(EditorSeparator, {}),
|
|
525
|
+
showCharCount && /* @__PURE__ */ jsxRuntime.jsx("span", { className: charLimitClasses, children: maxLength !== void 0 ? `${charCount} / ${maxLength}` : `${charCount} ${charCount === 1 ? "Character" : "Characters"}` })
|
|
526
|
+
] })
|
|
527
|
+
] });
|
|
528
|
+
}
|
|
529
|
+
const Editor = Object.assign(EditorRoot, {
|
|
530
|
+
Toolbar: EditorToolbar,
|
|
531
|
+
ToolbarGroup: EditorToolbarGroup,
|
|
532
|
+
ToolbarButton: EditorToolbarButton,
|
|
533
|
+
Separator: EditorSeparator,
|
|
534
|
+
StatusIndicator: EditorStatusIndicator,
|
|
535
|
+
Content: EditorContentArea,
|
|
536
|
+
StatusBar: EditorStatusBar
|
|
537
|
+
});
|
|
538
|
+
exports.Editor = Editor;
|
|
539
|
+
exports.EditorContentArea = EditorContentArea;
|
|
540
|
+
exports.EditorRoot = EditorRoot;
|
|
541
|
+
exports.EditorSeparator = EditorSeparator;
|
|
542
|
+
exports.EditorStatusBar = EditorStatusBar;
|
|
543
|
+
exports.EditorStatusIndicator = EditorStatusIndicator;
|
|
544
|
+
exports.EditorToolbar = EditorToolbar;
|
|
545
|
+
exports.EditorToolbarButton = EditorToolbarButton;
|
|
546
|
+
exports.EditorToolbarGroup = EditorToolbarGroup;
|
|
547
|
+
exports.useEditorContext = useEditorContext;
|
|
548
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../../src/components/Editor/index.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport styles from './Editor.module.scss';\nimport {\n TextB,\n TextItalic,\n TextStrikethrough,\n LinkSimple,\n Code,\n ListBullets,\n ListNumbers,\n TextHOne,\n TextHTwo,\n TextHThree,\n Quotes,\n ArrowCounterClockwise,\n ArrowClockwise,\n} from '@phosphor-icons/react';\nimport { KEYBOARD_SHORTCUTS } from '../../utils/keyboard-shortcuts';\n\n// ============================================\n// Lazy-loaded dependency (TipTap)\n// ============================================\n\nlet _useEditor: ((config: Record<string, unknown>) => unknown) | null = null;\nlet _EditorContent: React.ComponentType<Record<string, unknown>> | null = null;\nlet _StarterKit: unknown = null;\nlet _LinkExtension: unknown = null;\nlet _tiptapLoaded = false;\nlet _tiptapFailed = false;\n\nfunction loadTipTapDeps() {\n if (_tiptapLoaded) return;\n _tiptapLoaded = true;\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const tiptapReact = require('@tiptap/react');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const starterKit = require('@tiptap/starter-kit');\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const linkExt = require('@tiptap/extension-link');\n\n _useEditor = tiptapReact.useEditor;\n _EditorContent = tiptapReact.EditorContent;\n _StarterKit = starterKit.default ?? starterKit.StarterKit ?? starterKit;\n _LinkExtension = linkExt.default ?? linkExt.Link ?? linkExt;\n } catch {\n _tiptapFailed = true;\n }\n}\n\n// ============================================\n// Types\n// ============================================\n\nexport type EditorFormat =\n | 'bold' | 'italic' | 'strikethrough' | 'link' | 'code'\n | 'bulletList' | 'orderedList'\n | 'heading1' | 'heading2' | 'heading3'\n | 'blockquote'\n | 'undo' | 'redo';\n\nexport type EditorSaveStatus = 'idle' | 'saving' | 'saved' | 'error';\n\nexport type EditorMode = 'rich' | 'markdown';\n\nexport type EditorSize = 'sm' | 'md' | 'lg';\n\nexport interface EditorProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'> {\n children?: React.ReactNode;\n /** Controlled value */\n value?: string;\n /** Default value for uncontrolled usage */\n defaultValue?: string;\n /** Called when content changes */\n onValueChange?: (value: string) => void;\n /** Placeholder text */\n placeholder?: string;\n /** Disable the editor */\n disabled?: boolean;\n /** Read-only mode */\n readOnly?: boolean;\n /** Which format buttons to show */\n formats?: EditorFormat[];\n /** Show default toolbar */\n toolbar?: boolean;\n /** Show default status bar */\n statusBar?: boolean;\n /** Auto-save callback */\n onAutoSave?: (value: string) => void;\n /** Auto-save interval in ms */\n autoSaveInterval?: number;\n /** Editor size preset */\n size?: EditorSize;\n /** Maximum character count (shows indicator in status bar) */\n maxLength?: number;\n}\n\nexport interface EditorToolbarProps {\n children: React.ReactNode;\n className?: string;\n}\n\nexport interface EditorToolbarGroupProps {\n children: React.ReactNode;\n 'aria-label'?: string;\n className?: string;\n}\n\nexport interface EditorToolbarButtonProps {\n /** Which format this button toggles */\n format: EditorFormat;\n className?: string;\n}\n\nexport interface EditorSeparatorProps {\n className?: string;\n}\n\nexport interface EditorStatusIndicatorProps {\n /** Override the save status from context */\n status?: EditorSaveStatus;\n /** Custom labels per status */\n labels?: Partial<Record<EditorSaveStatus, string>>;\n className?: string;\n}\n\nexport interface EditorContentProps {\n className?: string;\n}\n\nexport interface EditorStatusBarProps {\n /** Show word count */\n showWordCount?: boolean;\n /** Show character count */\n showCharCount?: boolean;\n className?: string;\n}\n\n// ============================================\n// Format metadata\n// ============================================\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst FORMAT_META: Record<EditorFormat, { icon: React.ComponentType<any>; label: string; shortcut: string }> = {\n bold: { icon: TextB, label: 'Bold', shortcut: KEYBOARD_SHORTCUTS.EDITOR_BOLD.label },\n italic: { icon: TextItalic, label: 'Italic', shortcut: KEYBOARD_SHORTCUTS.EDITOR_ITALIC.label },\n strikethrough: { icon: TextStrikethrough, label: 'Strikethrough', shortcut: KEYBOARD_SHORTCUTS.EDITOR_STRIKETHROUGH.label },\n link: { icon: LinkSimple, label: 'Link', shortcut: KEYBOARD_SHORTCUTS.EDITOR_LINK.label },\n code: { icon: Code, label: 'Code', shortcut: KEYBOARD_SHORTCUTS.EDITOR_CODE.label },\n bulletList: { icon: ListBullets, label: 'Bullet list', shortcut: KEYBOARD_SHORTCUTS.EDITOR_BULLET_LIST.label },\n orderedList: { icon: ListNumbers, label: 'Ordered list', shortcut: KEYBOARD_SHORTCUTS.EDITOR_ORDERED_LIST.label },\n heading1: { icon: TextHOne, label: 'Heading 1', shortcut: KEYBOARD_SHORTCUTS.EDITOR_HEADING1.label },\n heading2: { icon: TextHTwo, label: 'Heading 2', shortcut: KEYBOARD_SHORTCUTS.EDITOR_HEADING2.label },\n heading3: { icon: TextHThree, label: 'Heading 3', shortcut: KEYBOARD_SHORTCUTS.EDITOR_HEADING3.label },\n blockquote: { icon: Quotes, label: 'Blockquote', shortcut: KEYBOARD_SHORTCUTS.EDITOR_BLOCKQUOTE.label },\n undo: { icon: ArrowCounterClockwise, label: 'Undo', shortcut: KEYBOARD_SHORTCUTS.EDITOR_UNDO.label },\n redo: { icon: ArrowClockwise, label: 'Redo', shortcut: KEYBOARD_SHORTCUTS.EDITOR_REDO.label },\n};\n\nconst DEFAULT_FORMATS: EditorFormat[] = ['bold', 'italic', 'strikethrough', 'link', 'code', 'bulletList'];\n\n/** Formats that are actions (not toggles) — no aria-pressed, different disable logic */\nconst ACTION_FORMATS = new Set<EditorFormat>(['undo', 'redo']);\n\nconst DEFAULT_STATUS_LABELS: Record<EditorSaveStatus, string> = {\n idle: '',\n saving: 'SAVING...',\n saved: 'AUTO-SAVED',\n error: 'SAVE FAILED',\n};\n\n// ============================================\n// Markdown formatting helpers (textarea fallback)\n// ============================================\n\ninterface TextareaSelection {\n start: number;\n end: number;\n text: string;\n}\n\nfunction getSelection(textarea: HTMLTextAreaElement): TextareaSelection {\n return {\n start: textarea.selectionStart,\n end: textarea.selectionEnd,\n text: textarea.value.substring(textarea.selectionStart, textarea.selectionEnd),\n };\n}\n\nfunction wrapSelection(\n textarea: HTMLTextAreaElement,\n prefix: string,\n suffix: string,\n setValue: (v: string) => void,\n) {\n const sel = getSelection(textarea);\n const before = textarea.value.substring(0, sel.start);\n const after = textarea.value.substring(sel.end);\n const wrapped = `${prefix}${sel.text || 'text'}${suffix}`;\n const newValue = `${before}${wrapped}${after}`;\n setValue(newValue);\n\n requestAnimationFrame(() => {\n textarea.focus();\n const newStart = sel.start + prefix.length;\n const newEnd = newStart + (sel.text || 'text').length;\n textarea.setSelectionRange(newStart, newEnd);\n });\n}\n\nfunction applyMarkdownFormat(\n format: EditorFormat,\n textarea: HTMLTextAreaElement,\n setValue: (v: string) => void,\n) {\n switch (format) {\n case 'bold':\n wrapSelection(textarea, '**', '**', setValue);\n break;\n case 'italic':\n wrapSelection(textarea, '*', '*', setValue);\n break;\n case 'strikethrough':\n wrapSelection(textarea, '~~', '~~', setValue);\n break;\n case 'code':\n wrapSelection(textarea, '`', '`', setValue);\n break;\n case 'link': {\n const sel = getSelection(textarea);\n const linkText = sel.text || 'link text';\n const before = textarea.value.substring(0, sel.start);\n const after = textarea.value.substring(sel.end);\n const newValue = `${before}[${linkText}](url)${after}`;\n setValue(newValue);\n requestAnimationFrame(() => {\n textarea.focus();\n const urlStart = sel.start + linkText.length + 3;\n textarea.setSelectionRange(urlStart, urlStart + 3);\n });\n break;\n }\n case 'bulletList': {\n const sel = getSelection(textarea);\n const before = textarea.value.substring(0, sel.start);\n const after = textarea.value.substring(sel.end);\n const lines = (sel.text || 'item').split('\\n');\n const bulleted = lines.map((l) => `- ${l}`).join('\\n');\n const newValue = `${before}${bulleted}${after}`;\n setValue(newValue);\n break;\n }\n case 'orderedList': {\n const sel = getSelection(textarea);\n const before = textarea.value.substring(0, sel.start);\n const after = textarea.value.substring(sel.end);\n const lines = (sel.text || 'item').split('\\n');\n const numbered = lines.map((l, i) => `${i + 1}. ${l}`).join('\\n');\n const newValue = `${before}${numbered}${after}`;\n setValue(newValue);\n break;\n }\n case 'heading1':\n wrapSelection(textarea, '# ', '', setValue);\n break;\n case 'heading2':\n wrapSelection(textarea, '## ', '', setValue);\n break;\n case 'heading3':\n wrapSelection(textarea, '### ', '', setValue);\n break;\n case 'blockquote': {\n const sel = getSelection(textarea);\n const before = textarea.value.substring(0, sel.start);\n const after = textarea.value.substring(sel.end);\n const lines = (sel.text || 'quote').split('\\n');\n const quoted = lines.map((l) => `> ${l}`).join('\\n');\n const newValue = `${before}${quoted}${after}`;\n setValue(newValue);\n break;\n }\n case 'undo':\n case 'redo':\n // Undo/redo in textarea mode is handled natively by the browser\n break;\n }\n}\n\n// ============================================\n// Context\n// ============================================\n\ninterface EditorContextValue {\n value: string;\n setValue: (v: string) => void;\n placeholder: string;\n disabled: boolean;\n readOnly: boolean;\n formats: EditorFormat[];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n editor: any | null;\n mode: EditorMode;\n size: EditorSize;\n maxLength?: number;\n wordCount: number;\n charCount: number;\n toggleFormat: (f: EditorFormat) => void;\n isFormatActive: (f: EditorFormat) => boolean;\n saveStatus: EditorSaveStatus;\n contentRef: React.RefObject<HTMLTextAreaElement | null>;\n}\n\nconst EditorContext = React.createContext<EditorContextValue | null>(null);\n\nfunction useEditorContext() {\n const context = React.useContext(EditorContext);\n if (!context) {\n throw new Error('Editor compound components must be used within an Editor');\n }\n return context;\n}\n\n// ============================================\n// Hooks\n// ============================================\n\nfunction useControllableState<T>(\n controlledValue: T | undefined,\n defaultValue: T,\n onChange?: (value: T) => void,\n): [T, (value: T) => void] {\n const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue);\n const isControlled = controlledValue !== undefined;\n const value = isControlled ? controlledValue : uncontrolledValue;\n\n const setValue = React.useCallback(\n (newValue: T) => {\n if (!isControlled) {\n setUncontrolledValue(newValue);\n }\n onChange?.(newValue);\n },\n [isControlled, onChange],\n );\n\n return [value, setValue];\n}\n\nfunction countWords(text: string): number {\n const trimmed = text.trim();\n if (!trimmed) return 0;\n return trimmed.split(/\\s+/).length;\n}\n\n// ============================================\n// Components\n// ============================================\n\nfunction EditorRoot({\n children,\n value: controlledValue,\n defaultValue = '',\n onValueChange,\n placeholder = 'Start typing...',\n disabled = false,\n readOnly = false,\n formats = DEFAULT_FORMATS,\n toolbar = true,\n statusBar = true,\n onAutoSave,\n autoSaveInterval = 30000,\n size = 'md',\n maxLength,\n className,\n ...htmlProps\n}: EditorProps) {\n const contentRef = React.useRef<HTMLTextAreaElement>(null);\n\n const [value, setValue] = useControllableState(\n controlledValue,\n defaultValue,\n onValueChange,\n );\n\n const [saveStatus, setSaveStatus] = React.useState<EditorSaveStatus>('idle');\n\n // Try loading TipTap\n loadTipTapDeps();\n const hasTipTap = !_tiptapFailed && _useEditor && _EditorContent && _StarterKit;\n const mode: EditorMode = hasTipTap ? 'rich' : 'markdown';\n\n // TipTap editor instance (only when available)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const tiptapEditor: any = hasTipTap\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (_useEditor as any)({\n extensions: [\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (_StarterKit as any).configure({\n heading: { levels: [1, 2, 3] },\n blockquote: {},\n codeBlock: false,\n horizontalRule: false,\n hardBreak: false,\n }),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (_LinkExtension as any).configure({\n openOnClick: false,\n HTMLAttributes: { rel: 'noopener noreferrer', target: '_blank' },\n }),\n ],\n editorProps: {\n attributes: {\n role: 'textbox',\n 'aria-label': placeholder,\n 'aria-multiline': 'true',\n },\n },\n content: defaultValue || controlledValue || '',\n editable: !disabled && !readOnly,\n onUpdate: ({ editor: e }: { editor: { getHTML: () => string } }) => {\n const html = e.getHTML();\n setValue(html);\n },\n })\n : null;\n\n // Sync controlled value to TipTap\n React.useEffect(() => {\n if (tiptapEditor && controlledValue !== undefined) {\n const currentContent = tiptapEditor.getHTML();\n if (currentContent !== controlledValue) {\n tiptapEditor.commands.setContent(controlledValue, false);\n }\n }\n }, [controlledValue, tiptapEditor]);\n\n // Update editable state\n React.useEffect(() => {\n if (tiptapEditor) {\n tiptapEditor.setEditable(!disabled && !readOnly);\n }\n }, [tiptapEditor, disabled, readOnly]);\n\n // Auto-save\n React.useEffect(() => {\n if (!onAutoSave || !value) return;\n\n const timer = setTimeout(() => {\n setSaveStatus('saving');\n try {\n onAutoSave(value);\n setSaveStatus('saved');\n } catch {\n setSaveStatus('error');\n }\n }, autoSaveInterval);\n\n return () => clearTimeout(timer);\n }, [value, onAutoSave, autoSaveInterval]);\n\n const toggleFormat = React.useCallback(\n (format: EditorFormat) => {\n if (disabled || readOnly) return;\n\n if (tiptapEditor) {\n switch (format) {\n case 'bold':\n tiptapEditor.chain().focus().toggleBold().run();\n break;\n case 'italic':\n tiptapEditor.chain().focus().toggleItalic().run();\n break;\n case 'strikethrough':\n tiptapEditor.chain().focus().toggleStrike().run();\n break;\n case 'code':\n tiptapEditor.chain().focus().toggleCode().run();\n break;\n case 'bulletList':\n tiptapEditor.chain().focus().toggleBulletList().run();\n break;\n case 'orderedList':\n tiptapEditor.chain().focus().toggleOrderedList().run();\n break;\n case 'heading1':\n tiptapEditor.chain().focus().toggleHeading({ level: 1 }).run();\n break;\n case 'heading2':\n tiptapEditor.chain().focus().toggleHeading({ level: 2 }).run();\n break;\n case 'heading3':\n tiptapEditor.chain().focus().toggleHeading({ level: 3 }).run();\n break;\n case 'blockquote':\n tiptapEditor.chain().focus().toggleBlockquote().run();\n break;\n case 'undo':\n tiptapEditor.chain().focus().undo().run();\n break;\n case 'redo':\n tiptapEditor.chain().focus().redo().run();\n break;\n case 'link': {\n const previousUrl = tiptapEditor.getAttributes('link').href;\n if (previousUrl) {\n tiptapEditor.chain().focus().unsetLink().run();\n } else {\n const url = window.prompt('Enter URL');\n if (url) {\n tiptapEditor.chain().focus().setLink({ href: url }).run();\n }\n }\n break;\n }\n }\n } else if (contentRef.current) {\n applyMarkdownFormat(format, contentRef.current, setValue);\n }\n },\n [disabled, readOnly, tiptapEditor, setValue],\n );\n\n const isFormatActive = React.useCallback(\n (format: EditorFormat): boolean => {\n if (!tiptapEditor) return false;\n switch (format) {\n case 'bold':\n return tiptapEditor.isActive('bold');\n case 'italic':\n return tiptapEditor.isActive('italic');\n case 'strikethrough':\n return tiptapEditor.isActive('strike');\n case 'code':\n return tiptapEditor.isActive('code');\n case 'bulletList':\n return tiptapEditor.isActive('bulletList');\n case 'orderedList':\n return tiptapEditor.isActive('orderedList');\n case 'heading1':\n return tiptapEditor.isActive('heading', { level: 1 });\n case 'heading2':\n return tiptapEditor.isActive('heading', { level: 2 });\n case 'heading3':\n return tiptapEditor.isActive('heading', { level: 3 });\n case 'blockquote':\n return tiptapEditor.isActive('blockquote');\n case 'link':\n return tiptapEditor.isActive('link');\n case 'undo':\n case 'redo':\n return false; // Actions don't have active state\n default:\n return false;\n }\n },\n [tiptapEditor],\n );\n\n const wordCount = React.useMemo(() => {\n if (tiptapEditor) {\n const text = tiptapEditor.getText?.() ?? '';\n return countWords(text);\n }\n return countWords(value);\n }, [value, tiptapEditor]);\n\n const charCount = React.useMemo(() => {\n if (tiptapEditor) {\n return (tiptapEditor.getText?.() ?? '').length;\n }\n return value.length;\n }, [value, tiptapEditor]);\n\n const contextValue: EditorContextValue = {\n value,\n setValue,\n placeholder,\n disabled,\n readOnly,\n formats,\n editor: tiptapEditor,\n mode,\n size,\n maxLength,\n wordCount,\n charCount,\n toggleFormat,\n isFormatActive,\n saveStatus,\n contentRef,\n };\n\n const classes = [\n styles.editor,\n disabled && styles.disabled,\n readOnly && styles.readOnly,\n className,\n ].filter(Boolean).join(' ');\n\n const hasCustomChildren = children !== undefined;\n\n return (\n <EditorContext.Provider value={contextValue}>\n <div\n {...htmlProps}\n className={classes}\n data-disabled={disabled || undefined}\n data-readonly={readOnly || undefined}\n data-size={size}\n >\n {hasCustomChildren ? (\n children\n ) : (\n <>\n {toolbar && (\n <EditorToolbar>\n <EditorToolbarGroup aria-label=\"Text formatting\">\n {formats.map((f) => (\n <EditorToolbarButton key={f} format={f} />\n ))}\n </EditorToolbarGroup>\n </EditorToolbar>\n )}\n <EditorContentArea />\n {statusBar && <EditorStatusBar showWordCount showCharCount />}\n </>\n )}\n </div>\n </EditorContext.Provider>\n );\n}\n\nfunction EditorToolbar({ children, className }: EditorToolbarProps) {\n const classes = [styles.toolbar, className].filter(Boolean).join(' ');\n return (\n <div className={classes} role=\"toolbar\" aria-label=\"Editor formatting\">\n {children}\n </div>\n );\n}\n\nfunction EditorToolbarGroup({ children, 'aria-label': ariaLabel, className }: EditorToolbarGroupProps) {\n const classes = [styles.toolbarGroup, className].filter(Boolean).join(' ');\n return (\n <div className={classes} role=\"group\" aria-label={ariaLabel}>\n {children}\n </div>\n );\n}\n\nfunction EditorToolbarButton({ format, className }: EditorToolbarButtonProps) {\n const { toggleFormat, isFormatActive, disabled, readOnly, editor, mode } = useEditorContext();\n const meta = FORMAT_META[format];\n const isAction = ACTION_FORMATS.has(format);\n const active = isAction ? false : isFormatActive(format);\n const IconComponent = meta.icon;\n\n // Action buttons (undo/redo) have special disable logic\n let isDisabled = disabled || readOnly;\n if (isAction && !isDisabled) {\n if (mode === 'markdown') {\n // Undo/redo in textarea mode is handled natively by the browser\n isDisabled = true;\n } else if (editor) {\n isDisabled = format === 'undo' ? !editor.can().undo() : !editor.can().redo();\n }\n }\n\n const classes = [\n styles.toolbarButton,\n active && styles.toolbarButtonActive,\n className,\n ].filter(Boolean).join(' ');\n\n return (\n <button\n type=\"button\"\n className={classes}\n onClick={() => toggleFormat(format)}\n disabled={isDisabled}\n aria-label={meta.label}\n title={`${meta.label} (${meta.shortcut})`}\n {...(isAction ? {} : { 'aria-pressed': active })}\n >\n <IconComponent size={16} weight={active ? 'bold' : 'regular'} />\n </button>\n );\n}\n\nfunction EditorSeparator({ className }: EditorSeparatorProps) {\n const classes = [styles.separator, className].filter(Boolean).join(' ');\n return <div className={classes} role=\"separator\" aria-orientation=\"vertical\" />;\n}\n\nfunction EditorStatusIndicator({ status: statusOverride, labels, className }: EditorStatusIndicatorProps) {\n const { saveStatus } = useEditorContext();\n const status = statusOverride ?? saveStatus;\n const mergedLabels = { ...DEFAULT_STATUS_LABELS, ...labels };\n const label = mergedLabels[status];\n\n if (!label) return null;\n\n const classes = [\n styles.statusIndicator,\n status === 'error' && styles.statusError,\n className,\n ].filter(Boolean).join(' ');\n\n return (\n <span className={classes} aria-live=\"polite\" role=\"status\">\n {label}\n </span>\n );\n}\n\nfunction EditorContentArea({ className }: EditorContentProps) {\n const { value, setValue, placeholder, disabled, readOnly, editor, mode, contentRef } =\n useEditorContext();\n\n if (mode === 'rich' && editor && _EditorContent) {\n const TipTapContent = _EditorContent;\n const classes = [styles.content, styles.contentRich, className].filter(Boolean).join(' ');\n return (\n <div className={classes} data-placeholder={placeholder}>\n <TipTapContent editor={editor} />\n </div>\n );\n }\n\n // Textarea fallback for markdown mode\n const classes = [styles.content, className].filter(Boolean).join(' ');\n return (\n <div className={classes}>\n <textarea\n ref={contentRef}\n className={styles.contentTextarea}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n placeholder={placeholder}\n disabled={disabled}\n readOnly={readOnly}\n aria-label={placeholder}\n />\n </div>\n );\n}\n\nfunction EditorStatusBar({ showWordCount = true, showCharCount = true, className }: EditorStatusBarProps) {\n const { wordCount, charCount, maxLength } = useEditorContext();\n\n const classes = [styles.statusBar, className].filter(Boolean).join(' ');\n\n const isOverLimit = maxLength !== undefined && charCount > maxLength;\n const isNearLimit = maxLength !== undefined && !isOverLimit && charCount >= maxLength * 0.9;\n\n const charLimitClasses = [\n styles.statusBarItem,\n isNearLimit && styles.statusBarItemWarning,\n isOverLimit && styles.statusBarItemError,\n ].filter(Boolean).join(' ');\n\n return (\n <div className={classes} aria-label=\"Editor statistics\">\n <div className={styles.statusBarLeft} />\n <div className={styles.statusBarRight}>\n {showWordCount && (\n <span className={styles.statusBarItem}>\n {wordCount} {wordCount === 1 ? 'Word' : 'Words'}\n </span>\n )}\n {showWordCount && showCharCount && (\n <EditorSeparator />\n )}\n {showCharCount && (\n <span className={charLimitClasses}>\n {maxLength !== undefined\n ? `${charCount} / ${maxLength}`\n : `${charCount} ${charCount === 1 ? 'Character' : 'Characters'}`\n }\n </span>\n )}\n </div>\n </div>\n );\n}\n\n// ============================================\n// Export compound component\n// ============================================\n\nexport const Editor = Object.assign(EditorRoot, {\n Toolbar: EditorToolbar,\n ToolbarGroup: EditorToolbarGroup,\n ToolbarButton: EditorToolbarButton,\n Separator: EditorSeparator,\n StatusIndicator: EditorStatusIndicator,\n Content: EditorContentArea,\n StatusBar: EditorStatusBar,\n});\n\nexport {\n EditorRoot,\n EditorToolbar,\n EditorToolbarGroup,\n EditorToolbarButton,\n EditorSeparator,\n EditorStatusIndicator,\n EditorContentArea,\n EditorStatusBar,\n};\n\nexport { useEditorContext };\n"],"names":["TextB","KEYBOARD_SHORTCUTS","TextItalic","TextStrikethrough","LinkSimple","Code","ListBullets","ListNumbers","TextHOne","TextHTwo","TextHThree","Quotes","ArrowCounterClockwise","ArrowClockwise","React","styles","jsx","jsxs","Fragment","classes"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAI,aAAoE;AACxE,IAAI,iBAAsE;AAC1E,IAAI,cAAuB;AAC3B,IAAI,iBAA0B;AAC9B,IAAI,gBAAgB;AACpB,IAAI,gBAAgB;AAEpB,SAAS,iBAAiB;AACxB,MAAI,cAAe;AACnB,kBAAgB;AAChB,MAAI;AAEF,UAAM,cAAc,QAAQ,eAAe;AAE3C,UAAM,aAAa,QAAQ,qBAAqB;AAEhD,UAAM,UAAU,QAAQ,wBAAwB;AAEhD,iBAAa,YAAY;AACzB,qBAAiB,YAAY;AAC7B,kBAAc,WAAW,WAAW,WAAW,cAAc;AAC7D,qBAAiB,QAAQ,WAAW,QAAQ,QAAQ;AAAA,EACtD,QAAQ;AACN,oBAAgB;AAAA,EAClB;AACF;AA+FA,MAAM,cAAyG;AAAA,EAC7G,MAAM,EAAE,MAAMA,MAAAA,OAAO,OAAO,QAAQ,UAAUC,kBAAAA,mBAAmB,YAAY,MAAA;AAAA,EAC7E,QAAQ,EAAE,MAAMC,MAAAA,YAAY,OAAO,UAAU,UAAUD,kBAAAA,mBAAmB,cAAc,MAAA;AAAA,EACxF,eAAe,EAAE,MAAME,MAAAA,mBAAmB,OAAO,iBAAiB,UAAUF,kBAAAA,mBAAmB,qBAAqB,MAAA;AAAA,EACpH,MAAM,EAAE,MAAMG,MAAAA,YAAY,OAAO,QAAQ,UAAUH,kBAAAA,mBAAmB,YAAY,MAAA;AAAA,EAClF,MAAM,EAAE,MAAMI,MAAAA,MAAM,OAAO,QAAQ,UAAUJ,kBAAAA,mBAAmB,YAAY,MAAA;AAAA,EAC5E,YAAY,EAAE,MAAMK,MAAAA,aAAa,OAAO,eAAe,UAAUL,kBAAAA,mBAAmB,mBAAmB,MAAA;AAAA,EACvG,aAAa,EAAE,MAAMM,MAAAA,aAAa,OAAO,gBAAgB,UAAUN,kBAAAA,mBAAmB,oBAAoB,MAAA;AAAA,EAC1G,UAAU,EAAE,MAAMO,MAAAA,UAAU,OAAO,aAAa,UAAUP,kBAAAA,mBAAmB,gBAAgB,MAAA;AAAA,EAC7F,UAAU,EAAE,MAAMQ,MAAAA,UAAU,OAAO,aAAa,UAAUR,kBAAAA,mBAAmB,gBAAgB,MAAA;AAAA,EAC7F,UAAU,EAAE,MAAMS,MAAAA,YAAY,OAAO,aAAa,UAAUT,kBAAAA,mBAAmB,gBAAgB,MAAA;AAAA,EAC/F,YAAY,EAAE,MAAMU,MAAAA,QAAQ,OAAO,cAAc,UAAUV,kBAAAA,mBAAmB,kBAAkB,MAAA;AAAA,EAChG,MAAM,EAAE,MAAMW,MAAAA,uBAAuB,OAAO,QAAQ,UAAUX,kBAAAA,mBAAmB,YAAY,MAAA;AAAA,EAC7F,MAAM,EAAE,MAAMY,sBAAgB,OAAO,QAAQ,UAAUZ,qCAAmB,YAAY,MAAA;AACxF;AAEA,MAAM,kBAAkC,CAAC,QAAQ,UAAU,iBAAiB,QAAQ,QAAQ,YAAY;AAGxG,MAAM,iBAAiB,oBAAI,IAAkB,CAAC,QAAQ,MAAM,CAAC;AAE7D,MAAM,wBAA0D;AAAA,EAC9D,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AACT;AAYA,SAAS,aAAa,UAAkD;AACtE,SAAO;AAAA,IACL,OAAO,SAAS;AAAA,IAChB,KAAK,SAAS;AAAA,IACd,MAAM,SAAS,MAAM,UAAU,SAAS,gBAAgB,SAAS,YAAY;AAAA,EAAA;AAEjF;AAEA,SAAS,cACP,UACA,QACA,QACA,UACA;AACA,QAAM,MAAM,aAAa,QAAQ;AACjC,QAAM,SAAS,SAAS,MAAM,UAAU,GAAG,IAAI,KAAK;AACpD,QAAM,QAAQ,SAAS,MAAM,UAAU,IAAI,GAAG;AAC9C,QAAM,UAAU,GAAG,MAAM,GAAG,IAAI,QAAQ,MAAM,GAAG,MAAM;AACvD,QAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK;AAC5C,WAAS,QAAQ;AAEjB,wBAAsB,MAAM;AAC1B,aAAS,MAAA;AACT,UAAM,WAAW,IAAI,QAAQ,OAAO;AACpC,UAAM,SAAS,YAAY,IAAI,QAAQ,QAAQ;AAC/C,aAAS,kBAAkB,UAAU,MAAM;AAAA,EAC7C,CAAC;AACH;AAEA,SAAS,oBACP,QACA,UACA,UACA;AACA,UAAQ,QAAA;AAAA,IACN,KAAK;AACH,oBAAc,UAAU,MAAM,MAAM,QAAQ;AAC5C;AAAA,IACF,KAAK;AACH,oBAAc,UAAU,KAAK,KAAK,QAAQ;AAC1C;AAAA,IACF,KAAK;AACH,oBAAc,UAAU,MAAM,MAAM,QAAQ;AAC5C;AAAA,IACF,KAAK;AACH,oBAAc,UAAU,KAAK,KAAK,QAAQ;AAC1C;AAAA,IACF,KAAK,QAAQ;AACX,YAAM,MAAM,aAAa,QAAQ;AACjC,YAAM,WAAW,IAAI,QAAQ;AAC7B,YAAM,SAAS,SAAS,MAAM,UAAU,GAAG,IAAI,KAAK;AACpD,YAAM,QAAQ,SAAS,MAAM,UAAU,IAAI,GAAG;AAC9C,YAAM,WAAW,GAAG,MAAM,IAAI,QAAQ,SAAS,KAAK;AACpD,eAAS,QAAQ;AACjB,4BAAsB,MAAM;AAC1B,iBAAS,MAAA;AACT,cAAM,WAAW,IAAI,QAAQ,SAAS,SAAS;AAC/C,iBAAS,kBAAkB,UAAU,WAAW,CAAC;AAAA,MACnD,CAAC;AACD;AAAA,IACF;AAAA,IACA,KAAK,cAAc;AACjB,YAAM,MAAM,aAAa,QAAQ;AACjC,YAAM,SAAS,SAAS,MAAM,UAAU,GAAG,IAAI,KAAK;AACpD,YAAM,QAAQ,SAAS,MAAM,UAAU,IAAI,GAAG;AAC9C,YAAM,SAAS,IAAI,QAAQ,QAAQ,MAAM,IAAI;AAC7C,YAAM,WAAW,MAAM,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AACrD,YAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK;AAC7C,eAAS,QAAQ;AACjB;AAAA,IACF;AAAA,IACA,KAAK,eAAe;AAClB,YAAM,MAAM,aAAa,QAAQ;AACjC,YAAM,SAAS,SAAS,MAAM,UAAU,GAAG,IAAI,KAAK;AACpD,YAAM,QAAQ,SAAS,MAAM,UAAU,IAAI,GAAG;AAC9C,YAAM,SAAS,IAAI,QAAQ,QAAQ,MAAM,IAAI;AAC7C,YAAM,WAAW,MAAM,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AAChE,YAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK;AAC7C,eAAS,QAAQ;AACjB;AAAA,IACF;AAAA,IACA,KAAK;AACH,oBAAc,UAAU,MAAM,IAAI,QAAQ;AAC1C;AAAA,IACF,KAAK;AACH,oBAAc,UAAU,OAAO,IAAI,QAAQ;AAC3C;AAAA,IACF,KAAK;AACH,oBAAc,UAAU,QAAQ,IAAI,QAAQ;AAC5C;AAAA,IACF,KAAK,cAAc;AACjB,YAAM,MAAM,aAAa,QAAQ;AACjC,YAAM,SAAS,SAAS,MAAM,UAAU,GAAG,IAAI,KAAK;AACpD,YAAM,QAAQ,SAAS,MAAM,UAAU,IAAI,GAAG;AAC9C,YAAM,SAAS,IAAI,QAAQ,SAAS,MAAM,IAAI;AAC9C,YAAM,SAAS,MAAM,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI;AACnD,YAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK;AAC3C,eAAS,QAAQ;AACjB;AAAA,IACF;AAAA,EAIE;AAEN;AA0BA,MAAM,gBAAgBa,iBAAM,cAAyC,IAAI;AAEzE,SAAS,mBAAmB;AAC1B,QAAM,UAAUA,iBAAM,WAAW,aAAa;AAC9C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AACA,SAAO;AACT;AAMA,SAAS,qBACP,iBACA,cACA,UACyB;AACzB,QAAM,CAAC,mBAAmB,oBAAoB,IAAIA,iBAAM,SAAS,YAAY;AAC7E,QAAM,eAAe,oBAAoB;AACzC,QAAM,QAAQ,eAAe,kBAAkB;AAE/C,QAAM,WAAWA,iBAAM;AAAA,IACrB,CAAC,aAAgB;AACf,UAAI,CAAC,cAAc;AACjB,6BAAqB,QAAQ;AAAA,MAC/B;AACA,2CAAW;AAAA,IACb;AAAA,IACA,CAAC,cAAc,QAAQ;AAAA,EAAA;AAGzB,SAAO,CAAC,OAAO,QAAQ;AACzB;AAEA,SAAS,WAAW,MAAsB;AACxC,QAAM,UAAU,KAAK,KAAA;AACrB,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,MAAM,KAAK,EAAE;AAC9B;AAMA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA,OAAO;AAAA,EACP,eAAe;AAAA,EACf;AAAA,EACA,cAAc;AAAA,EACd,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV,UAAU;AAAA,EACV,YAAY;AAAA,EACZ;AAAA,EACA,mBAAmB;AAAA,EACnB,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAgB;AACd,QAAM,aAAaA,iBAAM,OAA4B,IAAI;AAEzD,QAAM,CAAC,OAAO,QAAQ,IAAI;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAGF,QAAM,CAAC,YAAY,aAAa,IAAIA,iBAAM,SAA2B,MAAM;AAG3E,iBAAA;AACA,QAAM,YAAY,CAAC,iBAAiB,cAAc,kBAAkB;AACpE,QAAM,OAAmB,YAAY,SAAS;AAI9C,QAAM,eAAoB;AAAA;AAAA,IAErB,WAAmB;AAAA,MAClB,YAAY;AAAA;AAAA,QAET,YAAoB,UAAU;AAAA,UAC7B,SAAS,EAAE,QAAQ,CAAC,GAAG,GAAG,CAAC,EAAA;AAAA,UAC3B,YAAY,CAAA;AAAA,UACZ,WAAW;AAAA,UACX,gBAAgB;AAAA,UAChB,WAAW;AAAA,QAAA,CACZ;AAAA;AAAA,QAEA,eAAuB,UAAU;AAAA,UAChC,aAAa;AAAA,UACb,gBAAgB,EAAE,KAAK,uBAAuB,QAAQ,SAAA;AAAA,QAAS,CAChE;AAAA,MAAA;AAAA,MAEH,aAAa;AAAA,QACX,YAAY;AAAA,UACV,MAAM;AAAA,UACN,cAAc;AAAA,UACd,kBAAkB;AAAA,QAAA;AAAA,MACpB;AAAA,MAEF,SAAS,gBAAgB,mBAAmB;AAAA,MAC5C,UAAU,CAAC,YAAY,CAAC;AAAA,MACxB,UAAU,CAAC,EAAE,QAAQ,QAA+C;AAClE,cAAM,OAAO,EAAE,QAAA;AACf,iBAAS,IAAI;AAAA,MACf;AAAA,IAAA,CACD;AAAA,MACD;AAGJA,mBAAM,UAAU,MAAM;AACpB,QAAI,gBAAgB,oBAAoB,QAAW;AACjD,YAAM,iBAAiB,aAAa,QAAA;AACpC,UAAI,mBAAmB,iBAAiB;AACtC,qBAAa,SAAS,WAAW,iBAAiB,KAAK;AAAA,MACzD;AAAA,IACF;AAAA,EACF,GAAG,CAAC,iBAAiB,YAAY,CAAC;AAGlCA,mBAAM,UAAU,MAAM;AACpB,QAAI,cAAc;AAChB,mBAAa,YAAY,CAAC,YAAY,CAAC,QAAQ;AAAA,IACjD;AAAA,EACF,GAAG,CAAC,cAAc,UAAU,QAAQ,CAAC;AAGrCA,mBAAM,UAAU,MAAM;AACpB,QAAI,CAAC,cAAc,CAAC,MAAO;AAE3B,UAAM,QAAQ,WAAW,MAAM;AAC7B,oBAAc,QAAQ;AACtB,UAAI;AACF,mBAAW,KAAK;AAChB,sBAAc,OAAO;AAAA,MACvB,QAAQ;AACN,sBAAc,OAAO;AAAA,MACvB;AAAA,IACF,GAAG,gBAAgB;AAEnB,WAAO,MAAM,aAAa,KAAK;AAAA,EACjC,GAAG,CAAC,OAAO,YAAY,gBAAgB,CAAC;AAExC,QAAM,eAAeA,iBAAM;AAAA,IACzB,CAAC,WAAyB;AACxB,UAAI,YAAY,SAAU;AAE1B,UAAI,cAAc;AAChB,gBAAQ,QAAA;AAAA,UACN,KAAK;AACH,yBAAa,QAAQ,MAAA,EAAQ,WAAA,EAAa,IAAA;AAC1C;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,MAAA,EAAQ,aAAA,EAAe,IAAA;AAC5C;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,MAAA,EAAQ,aAAA,EAAe,IAAA;AAC5C;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,MAAA,EAAQ,WAAA,EAAa,IAAA;AAC1C;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,MAAA,EAAQ,iBAAA,EAAmB,IAAA;AAChD;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,MAAA,EAAQ,kBAAA,EAAoB,IAAA;AACjD;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,QAAQ,cAAc,EAAE,OAAO,GAAG,EAAE,IAAA;AACzD;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,QAAQ,cAAc,EAAE,OAAO,GAAG,EAAE,IAAA;AACzD;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,QAAQ,cAAc,EAAE,OAAO,GAAG,EAAE,IAAA;AACzD;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,MAAA,EAAQ,iBAAA,EAAmB,IAAA;AAChD;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,MAAA,EAAQ,KAAA,EAAO,IAAA;AACpC;AAAA,UACF,KAAK;AACH,yBAAa,QAAQ,MAAA,EAAQ,KAAA,EAAO,IAAA;AACpC;AAAA,UACF,KAAK,QAAQ;AACX,kBAAM,cAAc,aAAa,cAAc,MAAM,EAAE;AACvD,gBAAI,aAAa;AACf,2BAAa,QAAQ,MAAA,EAAQ,UAAA,EAAY,IAAA;AAAA,YAC3C,OAAO;AACL,oBAAM,MAAM,OAAO,OAAO,WAAW;AACrC,kBAAI,KAAK;AACP,6BAAa,QAAQ,QAAQ,QAAQ,EAAE,MAAM,KAAK,EAAE,IAAA;AAAA,cACtD;AAAA,YACF;AACA;AAAA,UACF;AAAA,QAAA;AAAA,MAEJ,WAAW,WAAW,SAAS;AAC7B,4BAAoB,QAAQ,WAAW,SAAS,QAAQ;AAAA,MAC1D;AAAA,IACF;AAAA,IACA,CAAC,UAAU,UAAU,cAAc,QAAQ;AAAA,EAAA;AAG7C,QAAM,iBAAiBA,iBAAM;AAAA,IAC3B,CAAC,WAAkC;AACjC,UAAI,CAAC,aAAc,QAAO;AAC1B,cAAQ,QAAA;AAAA,QACN,KAAK;AACH,iBAAO,aAAa,SAAS,MAAM;AAAA,QACrC,KAAK;AACH,iBAAO,aAAa,SAAS,QAAQ;AAAA,QACvC,KAAK;AACH,iBAAO,aAAa,SAAS,QAAQ;AAAA,QACvC,KAAK;AACH,iBAAO,aAAa,SAAS,MAAM;AAAA,QACrC,KAAK;AACH,iBAAO,aAAa,SAAS,YAAY;AAAA,QAC3C,KAAK;AACH,iBAAO,aAAa,SAAS,aAAa;AAAA,QAC5C,KAAK;AACH,iBAAO,aAAa,SAAS,WAAW,EAAE,OAAO,GAAG;AAAA,QACtD,KAAK;AACH,iBAAO,aAAa,SAAS,WAAW,EAAE,OAAO,GAAG;AAAA,QACtD,KAAK;AACH,iBAAO,aAAa,SAAS,WAAW,EAAE,OAAO,GAAG;AAAA,QACtD,KAAK;AACH,iBAAO,aAAa,SAAS,YAAY;AAAA,QAC3C,KAAK;AACH,iBAAO,aAAa,SAAS,MAAM;AAAA,QACrC,KAAK;AAAA,QACL,KAAK;AACH,iBAAO;AAAA;AAAA,QACT;AACE,iBAAO;AAAA,MAAA;AAAA,IAEb;AAAA,IACA,CAAC,YAAY;AAAA,EAAA;AAGf,QAAM,YAAYA,iBAAM,QAAQ,MAAM;;AACpC,QAAI,cAAc;AAChB,YAAM,SAAO,kBAAa,YAAb,0CAA4B;AACzC,aAAO,WAAW,IAAI;AAAA,IACxB;AACA,WAAO,WAAW,KAAK;AAAA,EACzB,GAAG,CAAC,OAAO,YAAY,CAAC;AAExB,QAAM,YAAYA,iBAAM,QAAQ,MAAM;;AACpC,QAAI,cAAc;AAChB,gBAAQ,kBAAa,YAAb,0CAA4B,IAAI;AAAA,IAC1C;AACA,WAAO,MAAM;AAAA,EACf,GAAG,CAAC,OAAO,YAAY,CAAC;AAExB,QAAM,eAAmC;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAGF,QAAM,UAAU;AAAA,IACdC,cAAAA,QAAO;AAAA,IACP,YAAYA,cAAAA,QAAO;AAAA,IACnB,YAAYA,cAAAA,QAAO;AAAA,IACnB;AAAA,EAAA,EACA,OAAO,OAAO,EAAE,KAAK,GAAG;AAE1B,QAAM,oBAAoB,aAAa;AAEvC,SACEC,2BAAAA,IAAC,cAAc,UAAd,EAAuB,OAAO,cAC7B,UAAAA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,WAAW;AAAA,MACX,iBAAe,YAAY;AAAA,MAC3B,iBAAe,YAAY;AAAA,MAC3B,aAAW;AAAA,MAEV,UAAA,oBACC,WAEAC,2BAAAA,KAAAC,WAAAA,UAAA,EACG,UAAA;AAAA,QAAA,0CACE,eAAA,EACC,UAAAF,+BAAC,oBAAA,EAAmB,cAAW,mBAC5B,UAAA,QAAQ,IAAI,CAAC,qCACX,qBAAA,EAA4B,QAAQ,KAAX,CAAc,CACzC,GACH,GACF;AAAA,uCAED,mBAAA,EAAkB;AAAA,QAClB,aAAaA,2BAAAA,IAAC,iBAAA,EAAgB,eAAa,MAAC,eAAa,KAAA,CAAC;AAAA,MAAA,EAAA,CAC7D;AAAA,IAAA;AAAA,EAAA,GAGN;AAEJ;AAEA,SAAS,cAAc,EAAE,UAAU,aAAiC;AAClE,QAAM,UAAU,CAACD,cAAAA,QAAO,SAAS,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACpE,SACEC,+BAAC,SAAI,WAAW,SAAS,MAAK,WAAU,cAAW,qBAChD,UACH;AAEJ;AAEA,SAAS,mBAAmB,EAAE,UAAU,cAAc,WAAW,aAAsC;AACrG,QAAM,UAAU,CAACD,cAAAA,QAAO,cAAc,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACzE,SACEC,+BAAC,SAAI,WAAW,SAAS,MAAK,SAAQ,cAAY,WAC/C,UACH;AAEJ;AAEA,SAAS,oBAAoB,EAAE,QAAQ,aAAuC;AAC5E,QAAM,EAAE,cAAc,gBAAgB,UAAU,UAAU,QAAQ,KAAA,IAAS,iBAAA;AAC3E,QAAM,OAAO,YAAY,MAAM;AAC/B,QAAM,WAAW,eAAe,IAAI,MAAM;AAC1C,QAAM,SAAS,WAAW,QAAQ,eAAe,MAAM;AACvD,QAAM,gBAAgB,KAAK;AAG3B,MAAI,aAAa,YAAY;AAC7B,MAAI,YAAY,CAAC,YAAY;AAC3B,QAAI,SAAS,YAAY;AAEvB,mBAAa;AAAA,IACf,WAAW,QAAQ;AACjB,mBAAa,WAAW,SAAS,CAAC,OAAO,IAAA,EAAM,KAAA,IAAS,CAAC,OAAO,IAAA,EAAM,KAAA;AAAA,IACxE;AAAA,EACF;AAEA,QAAM,UAAU;AAAA,IACdD,cAAAA,QAAO;AAAA,IACP,UAAUA,cAAAA,QAAO;AAAA,IACjB;AAAA,EAAA,EACA,OAAO,OAAO,EAAE,KAAK,GAAG;AAE1B,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW;AAAA,MACX,SAAS,MAAM,aAAa,MAAM;AAAA,MAClC,UAAU;AAAA,MACV,cAAY,KAAK;AAAA,MACjB,OAAO,GAAG,KAAK,KAAK,KAAK,KAAK,QAAQ;AAAA,MACrC,GAAI,WAAW,CAAA,IAAK,EAAE,gBAAgB,OAAA;AAAA,MAEvC,yCAAC,eAAA,EAAc,MAAM,IAAI,QAAQ,SAAS,SAAS,UAAA,CAAW;AAAA,IAAA;AAAA,EAAA;AAGpE;AAEA,SAAS,gBAAgB,EAAE,aAAmC;AAC5D,QAAM,UAAU,CAACD,cAAAA,QAAO,WAAW,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACtE,wCAAQ,OAAA,EAAI,WAAW,SAAS,MAAK,aAAY,oBAAiB,YAAW;AAC/E;AAEA,SAAS,sBAAsB,EAAE,QAAQ,gBAAgB,QAAQ,aAAyC;AACxG,QAAM,EAAE,WAAA,IAAe,iBAAA;AACvB,QAAM,SAAS,kBAAkB;AACjC,QAAM,eAAe,EAAE,GAAG,uBAAuB,GAAG,OAAA;AACpD,QAAM,QAAQ,aAAa,MAAM;AAEjC,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,UAAU;AAAA,IACdA,cAAAA,QAAO;AAAA,IACP,WAAW,WAAWA,cAAAA,QAAO;AAAA,IAC7B;AAAA,EAAA,EACA,OAAO,OAAO,EAAE,KAAK,GAAG;AAE1B,SACEC,+BAAC,UAAK,WAAW,SAAS,aAAU,UAAS,MAAK,UAC/C,UAAA,MAAA,CACH;AAEJ;AAEA,SAAS,kBAAkB,EAAE,aAAiC;AAC5D,QAAM,EAAE,OAAO,UAAU,aAAa,UAAU,UAAU,QAAQ,MAAM,WAAA,IACtE,iBAAA;AAEF,MAAI,SAAS,UAAU,UAAU,gBAAgB;AAC/C,UAAM,gBAAgB;AACtB,UAAMG,WAAU,CAACJ,sBAAO,SAASA,cAAAA,QAAO,aAAa,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACxF,WACEC,2BAAAA,IAAC,SAAI,WAAWG,UAAS,oBAAkB,aACzC,UAAAH,2BAAAA,IAAC,eAAA,EAAc,OAAA,CAAgB,EAAA,CACjC;AAAA,EAEJ;AAGA,QAAM,UAAU,CAACD,cAAAA,QAAO,SAAS,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACpE,SACEC,2BAAAA,IAAC,OAAA,EAAI,WAAW,SACd,UAAAA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAWD,cAAAA,QAAO;AAAA,MAClB;AAAA,MACA,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,MACxC;AAAA,MACA;AAAA,MACA;AAAA,MACA,cAAY;AAAA,IAAA;AAAA,EAAA,GAEhB;AAEJ;AAEA,SAAS,gBAAgB,EAAE,gBAAgB,MAAM,gBAAgB,MAAM,aAAmC;AACxG,QAAM,EAAE,WAAW,WAAW,UAAA,IAAc,iBAAA;AAE5C,QAAM,UAAU,CAACA,cAAAA,QAAO,WAAW,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAEtE,QAAM,cAAc,cAAc,UAAa,YAAY;AAC3D,QAAM,cAAc,cAAc,UAAa,CAAC,eAAe,aAAa,YAAY;AAExF,QAAM,mBAAmB;AAAA,IACvBA,cAAAA,QAAO;AAAA,IACP,eAAeA,cAAAA,QAAO;AAAA,IACtB,eAAeA,cAAAA,QAAO;AAAA,EAAA,EACtB,OAAO,OAAO,EAAE,KAAK,GAAG;AAE1B,SACEE,2BAAAA,KAAC,OAAA,EAAI,WAAW,SAAS,cAAW,qBAClC,UAAA;AAAA,IAAAD,2BAAAA,IAAC,OAAA,EAAI,WAAWD,cAAAA,QAAO,cAAA,CAAe;AAAA,IACtCE,2BAAAA,KAAC,OAAA,EAAI,WAAWF,cAAAA,QAAO,gBACpB,UAAA;AAAA,MAAA,iBACCE,2BAAAA,KAAC,QAAA,EAAK,WAAWF,cAAAA,QAAO,eACrB,UAAA;AAAA,QAAA;AAAA,QAAU;AAAA,QAAE,cAAc,IAAI,SAAS;AAAA,MAAA,GAC1C;AAAA,MAED,iBAAiB,iBAChBC,+BAAC,iBAAA,CAAA,CAAgB;AAAA,MAElB,iBACCA,2BAAAA,IAAC,QAAA,EAAK,WAAW,kBACd,UAAA,cAAc,SACX,GAAG,SAAS,MAAM,SAAS,KAC3B,GAAG,SAAS,IAAI,cAAc,IAAI,cAAc,YAAY,GAAA,CAElE;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;AAMO,MAAM,SAAS,OAAO,OAAO,YAAY;AAAA,EAC9C,SAAS;AAAA,EACT,cAAc;AAAA,EACd,eAAe;AAAA,EACf,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,WAAW;AACb,CAAC;;;;;;;;;;;"}
|