@marianmeres/stuic 3.135.0 → 3.137.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CommentInput/CommentInput.svelte +191 -348
- package/dist/components/CommentInput/CommentInput.svelte.d.ts +43 -44
- package/dist/components/CommentInput/README.md +104 -77
- package/dist/components/CommentInput/index.css +29 -259
- package/dist/components/CommentInput/index.d.ts +1 -2
- package/dist/components/CommentInput/index.js +1 -1
- package/dist/components/MarkdownEditor/MarkdownEditor.svelte +10 -0
- package/dist/components/MarkdownEditor/MarkdownEditor.svelte.d.ts +1 -0
- package/package.json +1 -1
- package/dist/components/CommentInput/_internal/render.d.ts +0 -24
- package/dist/components/CommentInput/_internal/render.js +0 -47
|
@@ -1,65 +1,84 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
import type {
|
|
4
|
-
|
|
3
|
+
import type {
|
|
4
|
+
ValidateOptions,
|
|
5
|
+
ValidationResult,
|
|
6
|
+
} from "../../actions/validate.svelte.js";
|
|
5
7
|
import type { THC } from "../Thc/Thc.svelte";
|
|
6
8
|
import type { InputWrapClassProps } from "../Input/types.js";
|
|
7
|
-
import
|
|
9
|
+
import { DEFAULT_MOBILE_TOOLBAR } from "../MarkdownEditor/index.js";
|
|
10
|
+
import type {
|
|
11
|
+
MarkdownEditorMode,
|
|
12
|
+
ToolbarItem,
|
|
13
|
+
PromptFn,
|
|
14
|
+
} from "../MarkdownEditor/index.js";
|
|
8
15
|
|
|
9
16
|
type SnippetWithId = Snippet<[{ id: string }]>;
|
|
10
17
|
|
|
11
|
-
/** Active
|
|
12
|
-
export type CommentInputMode =
|
|
18
|
+
/** Active editing surface of the comment box (mirrors `MarkdownEditor`). */
|
|
19
|
+
export type CommentInputMode = MarkdownEditorMode;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default toolbar for the comment box — mirrors the reduced mobile toolbar
|
|
23
|
+
* ({@link DEFAULT_MOBILE_TOOLBAR}) so the desktop and touch experiences stay
|
|
24
|
+
* consistent. Pass your own `toolbar` array to customize.
|
|
25
|
+
*/
|
|
26
|
+
export const DEFAULT_COMMENT_TOOLBAR: ToolbarItem[] = [
|
|
27
|
+
"bold",
|
|
28
|
+
"italic",
|
|
29
|
+
"|",
|
|
30
|
+
"bulletList",
|
|
31
|
+
"orderedList",
|
|
32
|
+
"|",
|
|
33
|
+
"link",
|
|
34
|
+
];
|
|
13
35
|
|
|
14
|
-
export interface Props
|
|
15
|
-
|
|
16
|
-
Omit<HTMLTextareaAttributes, "value" | "class" | "style">,
|
|
17
|
-
InputWrapClassProps {
|
|
18
|
-
/** Comment text (raw markdown). Bindable. */
|
|
36
|
+
export interface Props extends InputWrapClassProps {
|
|
37
|
+
/** Comment content (markdown). Bindable. */
|
|
19
38
|
value?: string;
|
|
20
|
-
/** Active
|
|
39
|
+
/** Active editing surface. Bindable. Default `"source"` (opens on raw markdown). */
|
|
21
40
|
mode?: CommentInputMode;
|
|
22
|
-
/** The
|
|
23
|
-
input?:
|
|
24
|
-
/** Name attribute of the
|
|
41
|
+
/** The editor surface wrapper element. Bindable (read). */
|
|
42
|
+
input?: HTMLDivElement;
|
|
43
|
+
/** Name attribute of the hidden input, for native form submission. */
|
|
25
44
|
name?: string;
|
|
26
45
|
//
|
|
27
46
|
label?: SnippetWithId | THC;
|
|
28
47
|
description?: SnippetWithId | THC;
|
|
29
48
|
class?: string;
|
|
30
49
|
id?: string;
|
|
31
|
-
tabindex?: number;
|
|
32
50
|
renderSize?: "sm" | "md" | "lg" | string;
|
|
33
51
|
required?: boolean;
|
|
34
52
|
disabled?: boolean;
|
|
35
53
|
placeholder?: string;
|
|
36
54
|
validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
|
|
37
|
-
/** Trim surrounding whitespace on blur. Default `true`. */
|
|
38
|
-
useTrim?: boolean;
|
|
39
|
-
/** Grow the textarea with its content (up to `max`). Default `true`. */
|
|
40
|
-
useAutogrow?: boolean | { enabled?: boolean; max?: number };
|
|
41
55
|
//
|
|
42
|
-
/** Show the Write/Preview tabs. Default `true`. Set `false` for a plain markdown textarea. */
|
|
43
|
-
showTabs?: boolean;
|
|
44
|
-
/** Label for the "write" tab. Default `"Write"`. */
|
|
45
|
-
writeLabel?: string;
|
|
46
|
-
/** Label for the "preview" tab. Default `"Preview"`. */
|
|
47
|
-
previewLabel?: string;
|
|
48
|
-
/** Show the subtle "Markdown supported" hint in the tab bar. Default `true`. */
|
|
49
|
-
showMarkdownHint?: boolean;
|
|
50
|
-
/** Text of the markdown hint. Default `"Markdown supported"`. */
|
|
51
|
-
markdownHintLabel?: string;
|
|
52
|
-
/** Placeholder shown in the Preview tab when there is nothing to render. */
|
|
53
|
-
previewEmptyLabel?: string;
|
|
54
|
-
/** Text shown in the Preview tab while the renderer is (lazy-)loading. */
|
|
55
|
-
previewLoadingLabel?: string;
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* is used (lazy-loaded; requires the optional `marked` / `dompurify` peers).
|
|
57
|
+
* Formatting toolbar. `true` shows {@link DEFAULT_COMMENT_TOOLBAR}, `false`
|
|
58
|
+
* hides it, or pass an ordered array of {@link ToolbarItem}s (`"|"` for a
|
|
59
|
+
* separator). Default {@link DEFAULT_COMMENT_TOOLBAR}.
|
|
61
60
|
*/
|
|
62
|
-
|
|
61
|
+
toolbar?: boolean | ToolbarItem[];
|
|
62
|
+
/** Toolbar used on mobile / touch devices. Same shape as `toolbar`. */
|
|
63
|
+
mobileToolbar?: boolean | ToolbarItem[];
|
|
64
|
+
/** Start in source mode on mobile / touch devices. Default `true`. */
|
|
65
|
+
autoSourceOnMobile?: boolean;
|
|
66
|
+
/** Media query defining "mobile". Default `"(pointer: coarse) and (max-width: 640px)"`. */
|
|
67
|
+
mobileQuery?: string;
|
|
68
|
+
/** URL prompt used by the link/image buttons. Defaults to `window.prompt`. */
|
|
69
|
+
prompt?: PromptFn;
|
|
70
|
+
/** Cap the editing surface height (long content scrolls inside). Default `32rem`. */
|
|
71
|
+
maxHeight?: number | string;
|
|
72
|
+
/** Also cap the surface to the parent's available height. Default `true`. */
|
|
73
|
+
capToParent?: boolean;
|
|
74
|
+
/** Show the WYSIWYG/Source mode toggle. Default `true`. */
|
|
75
|
+
showModeToggle?: boolean;
|
|
76
|
+
/** Toggle label while in WYSIWYG mode (switches to source). Default `"Source"`. */
|
|
77
|
+
sourceLabel?: string;
|
|
78
|
+
/** Toggle label while in source mode (switches to WYSIWYG). Default `"Preview"`. */
|
|
79
|
+
previewLabel?: string;
|
|
80
|
+
/** Wire ⌘/Ctrl+B / I / K to bold / italic / link. Default `true`. */
|
|
81
|
+
useShortcuts?: boolean;
|
|
63
82
|
//
|
|
64
83
|
/**
|
|
65
84
|
* Submit handler. Receives the current value. May be async — the submit
|
|
@@ -78,7 +97,7 @@
|
|
|
78
97
|
showSubmit?: boolean;
|
|
79
98
|
/** Show the cancel button. Defaults to `true` when `onCancel` is set. */
|
|
80
99
|
showCancel?: boolean;
|
|
81
|
-
/** Submit on ⌘/Ctrl+Enter while the
|
|
100
|
+
/** Submit on ⌘/Ctrl+Enter while the editor is focused. Default `true`. */
|
|
82
101
|
submitOnModEnter?: boolean;
|
|
83
102
|
/** Clear the value after a successful submit. Default `true`. */
|
|
84
103
|
clearOnSubmit?: boolean;
|
|
@@ -98,12 +117,10 @@
|
|
|
98
117
|
/** Extra content rendered in the footer, left of the buttons (e.g. attach). */
|
|
99
118
|
footer?: Snippet;
|
|
100
119
|
//
|
|
101
|
-
/** Classes for the
|
|
120
|
+
/** Classes for the editor surface element (forwarded to MarkdownEditor). */
|
|
102
121
|
classInput?: string;
|
|
103
|
-
/** Classes for the
|
|
122
|
+
/** Classes for the toolbar bar (forwarded to MarkdownEditor's `classToolbar`). */
|
|
104
123
|
classBar?: string;
|
|
105
|
-
/** Classes for the preview panel. */
|
|
106
|
-
classPreview?: string;
|
|
107
124
|
/** Classes for the footer (button row). */
|
|
108
125
|
classFooter?: string;
|
|
109
126
|
style?: string;
|
|
@@ -111,22 +128,15 @@
|
|
|
111
128
|
</script>
|
|
112
129
|
|
|
113
130
|
<script lang="ts">
|
|
114
|
-
import { autogrow } from "../../actions/autogrow.svelte.js";
|
|
115
|
-
import { trim } from "../../actions/trim.svelte.js";
|
|
116
|
-
import {
|
|
117
|
-
validate as validateAction,
|
|
118
|
-
type ValidationResult,
|
|
119
|
-
} from "../../actions/validate.svelte.js";
|
|
120
131
|
import { getId } from "../../utils/get-id.js";
|
|
121
132
|
import { twMerge } from "../../utils/tw-merge.js";
|
|
122
133
|
import { Button } from "../Button/index.js";
|
|
123
134
|
import Thc from "../Thc/Thc.svelte";
|
|
124
|
-
import
|
|
125
|
-
import { renderMarkdownSafe } from "./_internal/render.js";
|
|
135
|
+
import { MarkdownEditor } from "../MarkdownEditor/index.js";
|
|
126
136
|
|
|
127
137
|
let {
|
|
128
138
|
value = $bindable(""),
|
|
129
|
-
mode = $bindable("
|
|
139
|
+
mode = $bindable("source"),
|
|
130
140
|
input = $bindable(),
|
|
131
141
|
name,
|
|
132
142
|
//
|
|
@@ -134,24 +144,23 @@
|
|
|
134
144
|
description,
|
|
135
145
|
class: classProp,
|
|
136
146
|
id = getId(),
|
|
137
|
-
tabindex = 0,
|
|
138
147
|
renderSize = "md",
|
|
139
148
|
required = false,
|
|
140
149
|
disabled = false,
|
|
141
150
|
placeholder,
|
|
142
|
-
// Renamed to avoid collision with the exported `validate()` below.
|
|
143
151
|
validate: validateProp,
|
|
144
|
-
useTrim = true,
|
|
145
|
-
useAutogrow = true,
|
|
146
152
|
//
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
toolbar = DEFAULT_COMMENT_TOOLBAR,
|
|
154
|
+
mobileToolbar,
|
|
155
|
+
autoSourceOnMobile = true,
|
|
156
|
+
mobileQuery,
|
|
157
|
+
prompt,
|
|
158
|
+
maxHeight,
|
|
159
|
+
capToParent = true,
|
|
160
|
+
showModeToggle = true,
|
|
161
|
+
sourceLabel = "Source",
|
|
149
162
|
previewLabel = "Preview",
|
|
150
|
-
|
|
151
|
-
markdownHintLabel = "Markdown supported",
|
|
152
|
-
previewEmptyLabel = "Nothing to preview",
|
|
153
|
-
previewLoadingLabel = "Loading preview…",
|
|
154
|
-
renderMarkdown,
|
|
163
|
+
useShortcuts = true,
|
|
155
164
|
//
|
|
156
165
|
onSubmit,
|
|
157
166
|
onCancel,
|
|
@@ -176,7 +185,6 @@
|
|
|
176
185
|
//
|
|
177
186
|
classInput,
|
|
178
187
|
classBar,
|
|
179
|
-
classPreview,
|
|
180
188
|
classFooter,
|
|
181
189
|
classLabel,
|
|
182
190
|
classLabelBox,
|
|
@@ -188,109 +196,36 @@
|
|
|
188
196
|
classBelowBox,
|
|
189
197
|
classValidationBox,
|
|
190
198
|
style,
|
|
191
|
-
//
|
|
192
|
-
...rest
|
|
193
199
|
}: Props = $props();
|
|
194
200
|
|
|
195
|
-
//
|
|
196
|
-
let
|
|
197
|
-
const setValidationResult = (res: ValidationResult) => (validation = res);
|
|
198
|
-
let _doValidate: (() => void) | undefined = $state();
|
|
201
|
+
// The embedded editor instance (exposes validate/focus/command/… imperatively).
|
|
202
|
+
let editor = $state<MarkdownEditor>();
|
|
199
203
|
|
|
200
204
|
// --- Submit state ---------------------------------------------------------
|
|
201
205
|
let submitting = $state(false);
|
|
202
206
|
const isEmpty = $derived(!(value ?? "").trim());
|
|
203
|
-
// Resolve the show* defaults from the presence of their handlers.
|
|
204
207
|
const _showSubmit = $derived(showSubmit ?? !!onSubmit);
|
|
205
208
|
const _showCancel = $derived(showCancel ?? !!onCancel);
|
|
206
209
|
const _busy = $derived(disabled || busy || submitting);
|
|
207
210
|
const canSubmit = $derived(!_busy && !(submitDisabledWhenEmpty && isEmpty));
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
let previewLoading = $state(false);
|
|
213
|
-
|
|
214
|
-
$effect(() => {
|
|
215
|
-
if (mode !== "preview" || !showTabs) return;
|
|
216
|
-
const md = value ?? "";
|
|
217
|
-
if (!md.trim()) {
|
|
218
|
-
previewHtml = "";
|
|
219
|
-
previewError = false;
|
|
220
|
-
previewLoading = false;
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
const render = renderMarkdown ?? renderMarkdownSafe;
|
|
224
|
-
let cancelled = false;
|
|
225
|
-
previewLoading = true;
|
|
226
|
-
// Drop the previous value's HTML so the loading placeholder shows instead of
|
|
227
|
-
// briefly rendering content that no longer matches `value`.
|
|
228
|
-
previewHtml = "";
|
|
229
|
-
previewError = false;
|
|
230
|
-
(async () => {
|
|
231
|
-
try {
|
|
232
|
-
const html = await render(md);
|
|
233
|
-
if (cancelled) return;
|
|
234
|
-
previewHtml = html;
|
|
235
|
-
previewError = false;
|
|
236
|
-
} catch (err) {
|
|
237
|
-
if (cancelled) return;
|
|
238
|
-
previewHtml = "";
|
|
239
|
-
previewError = true;
|
|
240
|
-
// eslint-disable-next-line no-console
|
|
241
|
-
console.warn(
|
|
242
|
-
"[CommentInput] preview renderer failed. When using the default " +
|
|
243
|
-
"renderer, make sure the optional peers `marked` and `dompurify` " +
|
|
244
|
-
"are installed, or pass a `renderMarkdown` function.",
|
|
245
|
-
err
|
|
246
|
-
);
|
|
247
|
-
} finally {
|
|
248
|
-
if (!cancelled) previewLoading = false;
|
|
249
|
-
}
|
|
250
|
-
})();
|
|
251
|
-
return () => {
|
|
252
|
-
cancelled = true;
|
|
253
|
-
};
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// Returning to the Write tab: re-measure the textarea. autogrow can't size a
|
|
257
|
-
// `display:none` element, so an external value change made while previewing
|
|
258
|
-
// would otherwise leave the textarea at a stale height.
|
|
259
|
-
$effect(() => {
|
|
260
|
-
if (mode !== "write" || !input || !useAutogrow) return;
|
|
261
|
-
const el = input;
|
|
262
|
-
const max =
|
|
263
|
-
typeof useAutogrow === "object" && useAutogrow.max ? useAutogrow.max : 250;
|
|
264
|
-
requestAnimationFrame(() => {
|
|
265
|
-
el.style.height = "auto";
|
|
266
|
-
const cs = getComputedStyle(el);
|
|
267
|
-
const borderY =
|
|
268
|
-
cs.boxSizing === "border-box"
|
|
269
|
-
? parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth)
|
|
270
|
-
: 0;
|
|
271
|
-
el.style.height = Math.min(el.scrollHeight + borderY, max) + "px";
|
|
272
|
-
});
|
|
273
|
-
});
|
|
211
|
+
// Toggling `disabled` on MarkdownEditor remounts its backend, so only feed it
|
|
212
|
+
// the externally-controlled disable (not the transient `submitting`); the
|
|
213
|
+
// footer buttons carry the in-flight state instead.
|
|
214
|
+
const editorDisabled = $derived(disabled || busy);
|
|
274
215
|
|
|
275
216
|
// --- Handlers -------------------------------------------------------------
|
|
276
217
|
async function handleSubmit() {
|
|
277
218
|
if (!canSubmit) return;
|
|
278
219
|
// Validate before invoking the handler — the submit button bypasses any
|
|
279
220
|
// form-level submit check, so enforce it here (surfaces the inline message).
|
|
280
|
-
const res = validate();
|
|
281
|
-
if (res && !res.valid)
|
|
282
|
-
mode = "write";
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
221
|
+
const res = editor?.validate();
|
|
222
|
+
if (res && !res.valid) return;
|
|
285
223
|
try {
|
|
286
224
|
submitting = true;
|
|
287
225
|
await onSubmit?.(value ?? "");
|
|
288
226
|
if (clearOnSubmit) {
|
|
289
227
|
value = "";
|
|
290
|
-
|
|
291
|
-
if (mode === "preview") mode = "write";
|
|
292
|
-
validation = undefined;
|
|
293
|
-
input?.setCustomValidity?.("");
|
|
228
|
+
editor?.clearValidation();
|
|
294
229
|
}
|
|
295
230
|
} finally {
|
|
296
231
|
submitting = false;
|
|
@@ -301,60 +236,52 @@
|
|
|
301
236
|
onCancel?.();
|
|
302
237
|
}
|
|
303
238
|
|
|
239
|
+
// Capture keydown on the wrapper so we intercept the shortcut BEFORE the
|
|
240
|
+
// editor's own keymap (stopPropagation prevents a double-handle / native bold).
|
|
304
241
|
function handleKeydown(e: KeyboardEvent) {
|
|
305
|
-
if (
|
|
242
|
+
if (!(e.metaKey || e.ctrlKey)) return;
|
|
243
|
+
if (submitOnModEnter && e.key === "Enter") {
|
|
306
244
|
e.preventDefault();
|
|
245
|
+
e.stopPropagation();
|
|
307
246
|
handleSubmit();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (!useShortcuts) return;
|
|
250
|
+
const cmd =
|
|
251
|
+
e.key === "b" || e.key === "B"
|
|
252
|
+
? "bold"
|
|
253
|
+
: e.key === "i" || e.key === "I"
|
|
254
|
+
? "italic"
|
|
255
|
+
: e.key === "k" || e.key === "K"
|
|
256
|
+
? "link"
|
|
257
|
+
: undefined;
|
|
258
|
+
if (cmd) {
|
|
259
|
+
e.preventDefault();
|
|
260
|
+
e.stopPropagation();
|
|
261
|
+
editor?.command(cmd);
|
|
308
262
|
}
|
|
309
263
|
}
|
|
310
264
|
|
|
311
|
-
|
|
312
|
-
if (disabled) return;
|
|
313
|
-
mode = next;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Roving-tabindex keyboard nav for the Write/Preview tablist (automatic
|
|
317
|
-
// activation — Arrow/Home/End move selection and focus together).
|
|
318
|
-
function handleTabKeydown(e: KeyboardEvent) {
|
|
319
|
-
const keys = ["ArrowRight", "ArrowLeft", "ArrowDown", "ArrowUp", "Home", "End"];
|
|
320
|
-
if (!keys.includes(e.key)) return;
|
|
321
|
-
e.preventDefault();
|
|
322
|
-
const next: CommentInputMode =
|
|
323
|
-
e.key === "Home"
|
|
324
|
-
? "write"
|
|
325
|
-
: e.key === "End"
|
|
326
|
-
? "preview"
|
|
327
|
-
: mode === "write"
|
|
328
|
-
? "preview"
|
|
329
|
-
: "write";
|
|
330
|
-
setMode(next);
|
|
331
|
-
if (disabled) return;
|
|
332
|
-
document.getElementById(`${id}-tab-${next}`)?.focus();
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// --- Imperative API (mirrors FieldTextarea) -------------------------------
|
|
265
|
+
// --- Imperative API (delegates to the embedded MarkdownEditor) ------------
|
|
336
266
|
/** Trigger validation now. Renders the inline message if invalid. */
|
|
337
267
|
export function validate(): ValidationResult | undefined {
|
|
338
|
-
|
|
339
|
-
return validation;
|
|
268
|
+
return editor?.validate();
|
|
340
269
|
}
|
|
341
|
-
/** Clear the inline validation message
|
|
270
|
+
/** Clear the inline validation message. */
|
|
342
271
|
export function clearValidation(): void {
|
|
343
|
-
|
|
344
|
-
input?.setCustomValidity?.("");
|
|
272
|
+
editor?.clearValidation();
|
|
345
273
|
}
|
|
346
274
|
/** Current validation state, or undefined if the validator has never run. */
|
|
347
275
|
export function getValidation(): ValidationResult | undefined {
|
|
348
|
-
return
|
|
276
|
+
return editor?.getValidation();
|
|
349
277
|
}
|
|
350
|
-
/** Focus the
|
|
278
|
+
/** Focus the editor surface. */
|
|
351
279
|
export function focus(): void {
|
|
352
|
-
|
|
353
|
-
input?.focus?.();
|
|
280
|
+
editor?.focus();
|
|
354
281
|
}
|
|
355
282
|
/** Scroll the field into view. Defaults to smooth + center. */
|
|
356
283
|
export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
|
|
357
|
-
|
|
284
|
+
editor?.scrollIntoView(opts);
|
|
358
285
|
}
|
|
359
286
|
/** Programmatically run the submit flow (respects the empty/busy guards). */
|
|
360
287
|
export function submit(): void {
|
|
@@ -370,178 +297,94 @@
|
|
|
370
297
|
{/if}
|
|
371
298
|
{/snippet}
|
|
372
299
|
|
|
373
|
-
<
|
|
374
|
-
{
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
{id}
|
|
378
|
-
{label}
|
|
379
|
-
{labelAfter}
|
|
380
|
-
{below}
|
|
381
|
-
{required}
|
|
382
|
-
{disabled}
|
|
383
|
-
{labelLeft}
|
|
384
|
-
{labelLeftWidth}
|
|
385
|
-
{labelLeftBreakpoint}
|
|
386
|
-
{classLabel}
|
|
387
|
-
{classLabelBox}
|
|
388
|
-
{classInputBox}
|
|
389
|
-
{classInputBoxWrap}
|
|
390
|
-
{classInputBoxWrapInvalid}
|
|
391
|
-
{classDescBox}
|
|
392
|
-
{classDescBoxToggle}
|
|
393
|
-
{classBelowBox}
|
|
394
|
-
{classValidationBox}
|
|
395
|
-
{validation}
|
|
300
|
+
<div
|
|
301
|
+
class={twMerge("stuic-comment-input", classProp)}
|
|
302
|
+
data-size={renderSize}
|
|
303
|
+
class:disabled
|
|
396
304
|
{style}
|
|
305
|
+
onkeydowncapture={handleKeydown}
|
|
397
306
|
>
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
{/if}
|
|
307
|
+
{#if avatar}
|
|
308
|
+
<div class="stuic-comment-input-avatar">
|
|
309
|
+
{@render thcOrSnippet(avatar)}
|
|
310
|
+
</div>
|
|
311
|
+
{/if}
|
|
404
312
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
313
|
+
<div class="stuic-comment-input-main">
|
|
314
|
+
<MarkdownEditor
|
|
315
|
+
bind:this={editor}
|
|
316
|
+
bind:value
|
|
317
|
+
bind:mode
|
|
318
|
+
bind:input
|
|
319
|
+
{id}
|
|
320
|
+
{name}
|
|
321
|
+
{label}
|
|
322
|
+
{description}
|
|
323
|
+
{labelAfter}
|
|
324
|
+
{below}
|
|
325
|
+
{required}
|
|
326
|
+
disabled={editorDisabled}
|
|
327
|
+
{placeholder}
|
|
328
|
+
{renderSize}
|
|
329
|
+
validate={validateProp}
|
|
330
|
+
{toolbar}
|
|
331
|
+
{mobileToolbar}
|
|
332
|
+
{autoSourceOnMobile}
|
|
333
|
+
{mobileQuery}
|
|
334
|
+
{prompt}
|
|
335
|
+
{maxHeight}
|
|
336
|
+
{capToParent}
|
|
337
|
+
{showModeToggle}
|
|
338
|
+
{sourceLabel}
|
|
339
|
+
{previewLabel}
|
|
340
|
+
{labelLeft}
|
|
341
|
+
{labelLeftWidth}
|
|
342
|
+
{labelLeftBreakpoint}
|
|
343
|
+
classInput={twMerge("stuic-comment-input-editor", classInput)}
|
|
344
|
+
classToolbar={classBar}
|
|
345
|
+
{classLabel}
|
|
346
|
+
{classLabelBox}
|
|
347
|
+
{classInputBox}
|
|
348
|
+
{classInputBoxWrap}
|
|
349
|
+
{classInputBoxWrapInvalid}
|
|
350
|
+
{classDescBox}
|
|
351
|
+
{classDescBoxToggle}
|
|
352
|
+
{classBelowBox}
|
|
353
|
+
{classValidationBox}
|
|
354
|
+
onChange={(v) => onChange?.(v)}
|
|
355
|
+
/>
|
|
356
|
+
|
|
357
|
+
{#if _showSubmit || _showCancel || footer}
|
|
358
|
+
<div class={twMerge("stuic-comment-input-footer", classFooter)}>
|
|
359
|
+
<div class="stuic-comment-input-footer-start">
|
|
360
|
+
{@render footer?.()}
|
|
361
|
+
</div>
|
|
362
|
+
<div class="stuic-comment-input-footer-end">
|
|
363
|
+
{#if _showCancel}
|
|
364
|
+
<Button
|
|
365
|
+
type="button"
|
|
366
|
+
variant="ghost"
|
|
367
|
+
size={renderSize}
|
|
368
|
+
disabled={submitting}
|
|
369
|
+
onclick={handleCancel}
|
|
413
370
|
>
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
type="button"
|
|
417
|
-
role="tab"
|
|
418
|
-
aria-selected={mode === "write"}
|
|
419
|
-
aria-controls={`${id}-panel-write`}
|
|
420
|
-
tabindex={mode === "write" ? 0 : -1}
|
|
421
|
-
data-active={mode === "write" ? "true" : undefined}
|
|
422
|
-
{disabled}
|
|
423
|
-
onclick={() => setMode("write")}
|
|
424
|
-
onkeydown={handleTabKeydown}
|
|
425
|
-
>
|
|
426
|
-
{writeLabel}
|
|
427
|
-
</button>
|
|
428
|
-
<button
|
|
429
|
-
id={`${id}-tab-preview`}
|
|
430
|
-
type="button"
|
|
431
|
-
role="tab"
|
|
432
|
-
aria-selected={mode === "preview"}
|
|
433
|
-
aria-controls={`${id}-panel-preview`}
|
|
434
|
-
tabindex={mode === "preview" ? 0 : -1}
|
|
435
|
-
data-active={mode === "preview" ? "true" : undefined}
|
|
436
|
-
{disabled}
|
|
437
|
-
onclick={() => setMode("preview")}
|
|
438
|
-
onkeydown={handleTabKeydown}
|
|
439
|
-
>
|
|
440
|
-
{previewLabel}
|
|
441
|
-
</button>
|
|
442
|
-
</div>
|
|
371
|
+
{cancelLabel}
|
|
372
|
+
</Button>
|
|
443
373
|
{/if}
|
|
444
|
-
{#if
|
|
445
|
-
<
|
|
374
|
+
{#if _showSubmit}
|
|
375
|
+
<Button
|
|
376
|
+
type="button"
|
|
377
|
+
intent="primary"
|
|
378
|
+
size={renderSize}
|
|
379
|
+
disabled={!canSubmit}
|
|
380
|
+
spinner={submitting}
|
|
381
|
+
onclick={handleSubmit}
|
|
382
|
+
>
|
|
383
|
+
{submitLabel}
|
|
384
|
+
</Button>
|
|
446
385
|
{/if}
|
|
447
386
|
</div>
|
|
448
|
-
{/if}
|
|
449
|
-
|
|
450
|
-
<div class="stuic-comment-input-surface">
|
|
451
|
-
<div
|
|
452
|
-
class="stuic-comment-input-panel"
|
|
453
|
-
id={`${id}-panel-write`}
|
|
454
|
-
role={showTabs ? "tabpanel" : undefined}
|
|
455
|
-
aria-labelledby={showTabs ? `${id}-tab-write` : undefined}
|
|
456
|
-
hidden={showTabs && mode === "preview"}
|
|
457
|
-
>
|
|
458
|
-
<textarea
|
|
459
|
-
bind:value
|
|
460
|
-
bind:this={input}
|
|
461
|
-
{...rest}
|
|
462
|
-
{id}
|
|
463
|
-
{name}
|
|
464
|
-
{placeholder}
|
|
465
|
-
{tabindex}
|
|
466
|
-
{required}
|
|
467
|
-
disabled={_busy}
|
|
468
|
-
class={twMerge("stuic-comment-input-textarea", classInput)}
|
|
469
|
-
oninput={(e) => onChange?.(e.currentTarget.value)}
|
|
470
|
-
onkeydown={handleKeydown}
|
|
471
|
-
use:trim={() => ({
|
|
472
|
-
enabled: useTrim,
|
|
473
|
-
setValue: (v: string) => (value = v),
|
|
474
|
-
})}
|
|
475
|
-
use:validateAction={() => ({
|
|
476
|
-
enabled: validateProp !== false,
|
|
477
|
-
...(typeof validateProp === "boolean" ? {} : validateProp),
|
|
478
|
-
setValidationResult,
|
|
479
|
-
setDoValidate: (fn) => (_doValidate = fn),
|
|
480
|
-
})}
|
|
481
|
-
use:autogrow={() => ({
|
|
482
|
-
enabled: !!useAutogrow,
|
|
483
|
-
...(typeof useAutogrow === "boolean" ? {} : useAutogrow),
|
|
484
|
-
value,
|
|
485
|
-
})}
|
|
486
|
-
></textarea>
|
|
487
|
-
</div>
|
|
488
|
-
|
|
489
|
-
{#if showTabs && mode === "preview"}
|
|
490
|
-
<div
|
|
491
|
-
class={twMerge(
|
|
492
|
-
"stuic-comment-input-panel stuic-comment-input-preview",
|
|
493
|
-
classPreview
|
|
494
|
-
)}
|
|
495
|
-
id={`${id}-panel-preview`}
|
|
496
|
-
role="tabpanel"
|
|
497
|
-
aria-labelledby={`${id}-tab-preview`}
|
|
498
|
-
>
|
|
499
|
-
{#if previewError}
|
|
500
|
-
<p class="stuic-comment-input-preview-empty">Preview unavailable.</p>
|
|
501
|
-
{:else if !(value ?? "").trim()}
|
|
502
|
-
<p class="stuic-comment-input-preview-empty">{previewEmptyLabel}</p>
|
|
503
|
-
{:else if previewLoading && !previewHtml}
|
|
504
|
-
<p class="stuic-comment-input-preview-empty">{previewLoadingLabel}</p>
|
|
505
|
-
{:else}
|
|
506
|
-
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
507
|
-
{@html previewHtml}
|
|
508
|
-
{/if}
|
|
509
|
-
</div>
|
|
510
|
-
{/if}
|
|
511
387
|
</div>
|
|
512
|
-
|
|
513
|
-
{#if _showSubmit || _showCancel || footer}
|
|
514
|
-
<div class={twMerge("stuic-comment-input-footer", classFooter)}>
|
|
515
|
-
<div class="stuic-comment-input-footer-start">
|
|
516
|
-
{@render footer?.()}
|
|
517
|
-
</div>
|
|
518
|
-
<div class="stuic-comment-input-footer-end">
|
|
519
|
-
{#if _showCancel}
|
|
520
|
-
<Button
|
|
521
|
-
type="button"
|
|
522
|
-
variant="ghost"
|
|
523
|
-
size={renderSize}
|
|
524
|
-
disabled={submitting}
|
|
525
|
-
onclick={handleCancel}
|
|
526
|
-
>
|
|
527
|
-
{cancelLabel}
|
|
528
|
-
</Button>
|
|
529
|
-
{/if}
|
|
530
|
-
{#if _showSubmit}
|
|
531
|
-
<Button
|
|
532
|
-
type="button"
|
|
533
|
-
intent="primary"
|
|
534
|
-
size={renderSize}
|
|
535
|
-
disabled={!canSubmit}
|
|
536
|
-
spinner={submitting}
|
|
537
|
-
onclick={handleSubmit}
|
|
538
|
-
>
|
|
539
|
-
{submitLabel}
|
|
540
|
-
</Button>
|
|
541
|
-
{/if}
|
|
542
|
-
</div>
|
|
543
|
-
</div>
|
|
544
|
-
{/if}
|
|
545
|
-
</div>
|
|
388
|
+
{/if}
|
|
546
389
|
</div>
|
|
547
|
-
</
|
|
390
|
+
</div>
|