@marianmeres/stuic 3.136.0 → 3.138.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.
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
} from "../../actions/validate.svelte.js";
|
|
7
7
|
import type { THC } from "../Thc/Thc.svelte";
|
|
8
8
|
import type { InputWrapClassProps } from "../Input/types.js";
|
|
9
|
+
import { DEFAULT_MOBILE_TOOLBAR } from "../MarkdownEditor/index.js";
|
|
9
10
|
import type {
|
|
10
11
|
MarkdownEditorMode,
|
|
11
12
|
ToolbarItem,
|
|
@@ -18,14 +19,18 @@
|
|
|
18
19
|
export type CommentInputMode = MarkdownEditorMode;
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
*
|
|
22
|
-
* (
|
|
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.
|
|
23
25
|
*/
|
|
24
26
|
export const DEFAULT_COMMENT_TOOLBAR: ToolbarItem[] = [
|
|
25
27
|
"bold",
|
|
26
28
|
"italic",
|
|
29
|
+
"|",
|
|
30
|
+
"bulletList",
|
|
31
|
+
"orderedList",
|
|
32
|
+
"|",
|
|
27
33
|
"link",
|
|
28
|
-
"codeBlock",
|
|
29
34
|
];
|
|
30
35
|
|
|
31
36
|
export interface Props extends InputWrapClassProps {
|
|
@@ -96,8 +101,18 @@
|
|
|
96
101
|
submitOnModEnter?: boolean;
|
|
97
102
|
/** Clear the value after a successful submit. Default `true`. */
|
|
98
103
|
clearOnSubmit?: boolean;
|
|
99
|
-
/**
|
|
100
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Block submitting an empty/whitespace comment. Default `true`. When blocked,
|
|
106
|
+
* the native inline validation error is surfaced (the submit button stays
|
|
107
|
+
* enabled) instead of disabling the button — a disabled submit makes the whole
|
|
108
|
+
* box feel dead. Set `false` to let empty submits through to `onSubmit`.
|
|
109
|
+
*/
|
|
110
|
+
blockEmptySubmit?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Message rendered by the inline error when an empty submit is blocked (see
|
|
113
|
+
* {@link blockEmptySubmit}). Default `"Please write something before submitting."`.
|
|
114
|
+
*/
|
|
115
|
+
emptyMessage?: string;
|
|
101
116
|
/** Externally-controlled busy state (disables the box + submit). */
|
|
102
117
|
busy?: boolean;
|
|
103
118
|
//
|
|
@@ -166,7 +181,8 @@
|
|
|
166
181
|
showCancel,
|
|
167
182
|
submitOnModEnter = true,
|
|
168
183
|
clearOnSubmit = true,
|
|
169
|
-
|
|
184
|
+
blockEmptySubmit = true,
|
|
185
|
+
emptyMessage = "Please write something before submitting.",
|
|
170
186
|
busy = false,
|
|
171
187
|
//
|
|
172
188
|
labelAfter,
|
|
@@ -202,15 +218,61 @@
|
|
|
202
218
|
const _showSubmit = $derived(showSubmit ?? !!onSubmit);
|
|
203
219
|
const _showCancel = $derived(showCancel ?? !!onCancel);
|
|
204
220
|
const _busy = $derived(disabled || busy || submitting);
|
|
205
|
-
const canSubmit = $derived(!_busy && !(submitDisabledWhenEmpty && isEmpty));
|
|
206
221
|
// Toggling `disabled` on MarkdownEditor remounts its backend, so only feed it
|
|
207
222
|
// the externally-controlled disable (not the transient `submitting`); the
|
|
208
223
|
// footer buttons carry the in-flight state instead.
|
|
209
224
|
const editorDisabled = $derived(disabled || busy);
|
|
210
225
|
|
|
226
|
+
// Empty submits are blocked by surfacing the native inline error (NOT by
|
|
227
|
+
// disabling the button — a disabled submit makes the whole box feel dead).
|
|
228
|
+
// We arm this only for the duration of an explicit submit attempt, so a casual
|
|
229
|
+
// focus/blur of an empty box doesn't nag with an error. Plain (non-reactive)
|
|
230
|
+
// `let`: the validator closure reads it live, and we never want toggling it to
|
|
231
|
+
// recompute `_validate`.
|
|
232
|
+
let emptyCheckArmed = false;
|
|
233
|
+
|
|
234
|
+
// Validation options forwarded to MarkdownEditor. When `blockEmptySubmit` is on
|
|
235
|
+
// we splice an empty-check into the consumer's validator — preserving any custom
|
|
236
|
+
// rule plus the editor's own `required` handling — that fires only while armed.
|
|
237
|
+
const _validate = $derived.by(() => {
|
|
238
|
+
// Consumer disabled validation entirely: pass through untouched. There is no
|
|
239
|
+
// inline error to surface then; `handleSubmit` still refuses empty submits.
|
|
240
|
+
if (validateProp === false) return false;
|
|
241
|
+
if (!blockEmptySubmit) return validateProp;
|
|
242
|
+
const base: Omit<ValidateOptions, "setValidationResult"> =
|
|
243
|
+
typeof validateProp === "object" && validateProp ? validateProp : {};
|
|
244
|
+
const inner = base.customValidator;
|
|
245
|
+
return {
|
|
246
|
+
...base,
|
|
247
|
+
customValidator(
|
|
248
|
+
val: unknown,
|
|
249
|
+
ctx: Record<string, unknown> | undefined,
|
|
250
|
+
el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
251
|
+
) {
|
|
252
|
+
if (emptyCheckArmed && (val == null || String(val).trim() === "")) {
|
|
253
|
+
return emptyMessage;
|
|
254
|
+
}
|
|
255
|
+
return inner ? inner(val, ctx, el) || "" : "";
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
|
|
211
260
|
// --- Handlers -------------------------------------------------------------
|
|
212
261
|
async function handleSubmit() {
|
|
213
|
-
if (
|
|
262
|
+
if (_busy) return;
|
|
263
|
+
// Block an empty submit by surfacing the native inline error instead of
|
|
264
|
+
// disabling the button. Arm the empty-check just for this validation pass,
|
|
265
|
+
// then focus the box so the user can start typing.
|
|
266
|
+
if (blockEmptySubmit && isEmpty) {
|
|
267
|
+
emptyCheckArmed = true;
|
|
268
|
+
try {
|
|
269
|
+
editor?.validate();
|
|
270
|
+
} finally {
|
|
271
|
+
emptyCheckArmed = false;
|
|
272
|
+
}
|
|
273
|
+
editor?.focus();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
214
276
|
// Validate before invoking the handler — the submit button bypasses any
|
|
215
277
|
// form-level submit check, so enforce it here (surfaces the inline message).
|
|
216
278
|
const res = editor?.validate();
|
|
@@ -321,7 +383,7 @@
|
|
|
321
383
|
disabled={editorDisabled}
|
|
322
384
|
{placeholder}
|
|
323
385
|
{renderSize}
|
|
324
|
-
validate={
|
|
386
|
+
validate={_validate}
|
|
325
387
|
{toolbar}
|
|
326
388
|
{mobileToolbar}
|
|
327
389
|
{autoSourceOnMobile}
|
|
@@ -371,7 +433,7 @@
|
|
|
371
433
|
type="button"
|
|
372
434
|
intent="primary"
|
|
373
435
|
size={renderSize}
|
|
374
|
-
disabled={
|
|
436
|
+
disabled={_busy}
|
|
375
437
|
spinner={submitting}
|
|
376
438
|
onclick={handleSubmit}
|
|
377
439
|
>
|
|
@@ -9,8 +9,9 @@ type SnippetWithId = Snippet<[{
|
|
|
9
9
|
/** Active editing surface of the comment box (mirrors `MarkdownEditor`). */
|
|
10
10
|
export type CommentInputMode = MarkdownEditorMode;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* (
|
|
12
|
+
* Default toolbar for the comment box — mirrors the reduced mobile toolbar
|
|
13
|
+
* ({@link DEFAULT_MOBILE_TOOLBAR}) so the desktop and touch experiences stay
|
|
14
|
+
* consistent. Pass your own `toolbar` array to customize.
|
|
14
15
|
*/
|
|
15
16
|
export declare const DEFAULT_COMMENT_TOOLBAR: ToolbarItem[];
|
|
16
17
|
export interface Props extends InputWrapClassProps {
|
|
@@ -78,8 +79,18 @@ export interface Props extends InputWrapClassProps {
|
|
|
78
79
|
submitOnModEnter?: boolean;
|
|
79
80
|
/** Clear the value after a successful submit. Default `true`. */
|
|
80
81
|
clearOnSubmit?: boolean;
|
|
81
|
-
/**
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Block submitting an empty/whitespace comment. Default `true`. When blocked,
|
|
84
|
+
* the native inline validation error is surfaced (the submit button stays
|
|
85
|
+
* enabled) instead of disabling the button — a disabled submit makes the whole
|
|
86
|
+
* box feel dead. Set `false` to let empty submits through to `onSubmit`.
|
|
87
|
+
*/
|
|
88
|
+
blockEmptySubmit?: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Message rendered by the inline error when an empty submit is blocked (see
|
|
91
|
+
* {@link blockEmptySubmit}). Default `"Please write something before submitting."`.
|
|
92
|
+
*/
|
|
93
|
+
emptyMessage?: string;
|
|
83
94
|
/** Externally-controlled busy state (disables the box + submit). */
|
|
84
95
|
busy?: boolean;
|
|
85
96
|
labelAfter?: SnippetWithId | THC;
|
|
@@ -51,11 +51,11 @@ console error explains why.
|
|
|
51
51
|
|
|
52
52
|
## Toolbar
|
|
53
53
|
|
|
54
|
-
`toolbar` accepts `true` (the
|
|
54
|
+
`toolbar` accepts `true` (the default), `false` (hidden), or an ordered
|
|
55
55
|
array of items (`"|"` for a separator):
|
|
56
56
|
|
|
57
57
|
```svelte
|
|
58
|
-
<!--
|
|
58
|
+
<!-- Default: ["bold", "italic", "|", "bulletList", "orderedList", "|", "link"] -->
|
|
59
59
|
<CommentInput bind:value onSubmit={post} />
|
|
60
60
|
|
|
61
61
|
<!-- Custom set -->
|
|
@@ -82,43 +82,44 @@ Available items: `bold`, `italic`, `heading1`–`heading6`, `link`, `image`,
|
|
|
82
82
|
|
|
83
83
|
## Props
|
|
84
84
|
|
|
85
|
-
| Prop
|
|
86
|
-
|
|
|
87
|
-
| `value`
|
|
88
|
-
| `mode`
|
|
89
|
-
| `input`
|
|
90
|
-
| `name`
|
|
91
|
-
| `label`
|
|
92
|
-
| `description`
|
|
93
|
-
| `placeholder`
|
|
94
|
-
| `renderSize`
|
|
95
|
-
| `required`
|
|
96
|
-
| `disabled`
|
|
97
|
-
| `validate`
|
|
98
|
-
| `toolbar`
|
|
99
|
-
| `mobileToolbar`
|
|
100
|
-
| `autoSourceOnMobile`
|
|
101
|
-
| `mobileQuery`
|
|
102
|
-
| `prompt`
|
|
103
|
-
| `maxHeight`
|
|
104
|
-
| `capToParent`
|
|
105
|
-
| `showModeToggle`
|
|
106
|
-
| `sourceLabel`
|
|
107
|
-
| `previewLabel`
|
|
108
|
-
| `useShortcuts`
|
|
109
|
-
| `onSubmit`
|
|
110
|
-
| `onCancel`
|
|
111
|
-
| `onChange`
|
|
112
|
-
| `submitLabel`
|
|
113
|
-
| `cancelLabel`
|
|
114
|
-
| `showSubmit`
|
|
115
|
-
| `showCancel`
|
|
116
|
-
| `submitOnModEnter`
|
|
117
|
-
| `clearOnSubmit`
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
85
|
+
| Prop | Type | Default | Description |
|
|
86
|
+
| -------------------- | ------------------------------------------ | --------------------------------------------- | --------------------------------------------------------------------------- |
|
|
87
|
+
| `value` | `string` (bindable) | `""` | Comment content (markdown). |
|
|
88
|
+
| `mode` | `"wysiwyg" \| "source"` (bindable) | `"source"` | Active editing surface (opens on raw markdown). |
|
|
89
|
+
| `input` | `HTMLDivElement` (bindable) | — | The editor surface wrapper element. |
|
|
90
|
+
| `name` | `string` | — | Hidden input `name`, for native form submission. |
|
|
91
|
+
| `label` | `THC \| Snippet` | `""` | Field label (via InputWrap). |
|
|
92
|
+
| `description` | `THC \| Snippet` | — | Collapsible hint below the box. |
|
|
93
|
+
| `placeholder` | `string` | — | Editor placeholder. |
|
|
94
|
+
| `renderSize` | `"sm" \| "md" \| "lg" \| string` | `"md"` | Size variant. |
|
|
95
|
+
| `required` | `boolean` | `false` | Mark required + enforce on validation. |
|
|
96
|
+
| `disabled` | `boolean` | `false` | Disable the whole box. |
|
|
97
|
+
| `validate` | `boolean \| ValidateOptions` | — | Enable validation (same contract as `MarkdownEditor`). |
|
|
98
|
+
| `toolbar` | `boolean \| ToolbarItem[]` | `DEFAULT_COMMENT_TOOLBAR` | Formatting toolbar config. |
|
|
99
|
+
| `mobileToolbar` | `boolean \| ToolbarItem[]` | MarkdownEditor default | Toolbar on touch devices. |
|
|
100
|
+
| `autoSourceOnMobile` | `boolean` | `true` | Start in source mode on mobile. |
|
|
101
|
+
| `mobileQuery` | `string` | `(pointer: coarse) …` | Media query defining "mobile". |
|
|
102
|
+
| `prompt` | `PromptFn` | `window.prompt` | URL prompt used by the link/image buttons. |
|
|
103
|
+
| `maxHeight` | `number \| string` | `32rem` | Cap the editing surface height (scrolls inside). |
|
|
104
|
+
| `capToParent` | `boolean` | `true` | Also cap the surface to the parent's available height. |
|
|
105
|
+
| `showModeToggle` | `boolean` | `true` | Show the WYSIWYG/Source toggle. |
|
|
106
|
+
| `sourceLabel` | `string` | `"Source"` | Toggle label while in WYSIWYG mode. |
|
|
107
|
+
| `previewLabel` | `string` | `"Preview"` | Toggle label while in source mode. |
|
|
108
|
+
| `useShortcuts` | `boolean` | `true` | Wire ⌘/Ctrl+B / I / K to bold / italic / link. |
|
|
109
|
+
| `onSubmit` | `(value: string) => void \| Promise<void>` | — | Submit handler. Async → spinner + disabled while pending. |
|
|
110
|
+
| `onCancel` | `() => void` | — | Cancel handler. |
|
|
111
|
+
| `onChange` | `(value: string) => void` | — | Fired on every edit. |
|
|
112
|
+
| `submitLabel` | `string` | `"Comment"` | Submit button label. |
|
|
113
|
+
| `cancelLabel` | `string` | `"Cancel"` | Cancel button label. |
|
|
114
|
+
| `showSubmit` | `boolean` | `!!onSubmit` | Show the submit button. |
|
|
115
|
+
| `showCancel` | `boolean` | `!!onCancel` | Show the cancel button. |
|
|
116
|
+
| `submitOnModEnter` | `boolean` | `true` | Submit on ⌘/Ctrl+Enter. |
|
|
117
|
+
| `clearOnSubmit` | `boolean` | `true` | Clear value after a successful submit. |
|
|
118
|
+
| `blockEmptySubmit` | `boolean` | `true` | Block empty/whitespace submits via the inline error (submit stays enabled). |
|
|
119
|
+
| `emptyMessage` | `string` | `"Please write something before submitting."` | Inline error shown when an empty submit is blocked. |
|
|
120
|
+
| `busy` | `boolean` | `false` | External busy state (disables box + submit). |
|
|
121
|
+
| `avatar` | `THC \| Snippet` | — | Optional gutter to the left of the box. |
|
|
122
|
+
| `footer` | `Snippet` | — | Extra footer content, left of the buttons. |
|
|
122
123
|
|
|
123
124
|
Plus the standard `InputWrap` layout props (`labelAfter`, `below`, `labelLeft`,
|
|
124
125
|
`labelLeftWidth`, `labelLeftBreakpoint`) and `class*` props (`classInput` →
|