@marianmeres/stuic 3.135.0 → 3.136.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.
@@ -1,36 +1,23 @@
1
1
  /******************************************************************************
2
2
  CommentInput
3
3
 
4
- A GitHub-style comment box: a raw-markdown textarea with Write/Preview tabs and
5
- a submit footer. Field chrome (label/description/validation/sizing) comes from
6
- the shared InputWrap, so it matches the `Field*` family; everything specific to
7
- the box lives here.
8
-
9
- Tokens follow the library convention: a component token that falls back to the
10
- shared `--stuic-input-*` token, then to a global default — so the box inherits
11
- input theming for free but can be retargeted on its own.
4
+ A GitHub-style comment composer built on top of `MarkdownEditor` (a rich
5
+ WYSIWYG / source surface) with an avatar gutter and a submit/cancel footer.
6
+
7
+ The editor card, formatting toolbar, mode toggle and field chrome
8
+ (label / description / validation / sizing / theming) all come from
9
+ `MarkdownEditor` and its shared `InputWrap` so the box inherits the
10
+ `--stuic-markdown-editor-*` / `--stuic-input-*` theming for free. This
11
+ stylesheet only lays out the gutter + footer and tightens the spacing so the
12
+ editor and its buttons read as a single composer unit.
12
13
  ******************************************************************************/
13
14
 
14
15
  .stuic-comment-input {
15
- --_radius: var(
16
- --stuic-comment-input-radius,
17
- var(--stuic-input-radius, var(--stuic-radius))
18
- );
19
- --_border-color: var(
20
- --stuic-comment-input-border,
21
- var(--stuic-input-border, currentColor)
22
- );
23
- --_bg: var(--stuic-comment-input-bg, var(--stuic-input-bg, transparent));
24
- --_pad-x: var(
25
- --stuic-comment-input-padding-x,
26
- var(--stuic-input-padding-x-md, 0.75rem)
27
- );
28
- --_pad-y: var(--stuic-comment-input-padding-y, var(--stuic-input-padding-y-md, 0.5rem));
29
- --_min-height: var(--stuic-comment-input-min-height, 5rem);
30
- --_font-size: var(
31
- --stuic-comment-input-font-size,
32
- var(--stuic-input-font-size-md, 0.9375rem)
33
- );
16
+ /* The embedded MarkdownEditor defaults to a tall editing surface (12rem md);
17
+ for a comment composer we want the lighter, GitHub-like proportions of the
18
+ original box. Funnel a comment-scoped token into the editor's min-height var
19
+ so it stays themeable (and still overridable via the editor token directly). */
20
+ --stuic-markdown-editor-min-height: var(--stuic-comment-input-min-height, 5rem);
34
21
 
35
22
  display: flex;
36
23
  align-items: flex-start;
@@ -39,31 +26,24 @@
39
26
  min-width: 0;
40
27
  }
41
28
 
29
+ .stuic-comment-input[data-size="sm"] {
30
+ --stuic-comment-input-min-height: 4rem;
31
+ }
32
+ .stuic-comment-input[data-size="lg"] {
33
+ --stuic-comment-input-min-height: 7rem;
34
+ }
35
+
42
36
  .stuic-comment-input-avatar {
43
37
  flex: 0 0 auto;
44
38
  padding-top: 0.125rem;
45
39
  }
46
40
 
47
- /* The bordered card — focus ring lives here, not on the avatar row. */
48
- .stuic-comment-input-editor {
41
+ .stuic-comment-input-main {
49
42
  display: flex;
50
43
  flex-direction: column;
51
44
  flex: 1 1 auto;
52
45
  min-width: 0;
53
46
  max-width: 100%;
54
- background: var(--_bg);
55
- border: var(--stuic-border-width, 1px) solid var(--_border-color);
56
- border-radius: var(--_radius);
57
- color: var(--stuic-input-text, inherit);
58
- overflow: hidden;
59
- transition: border-color var(--stuic-transition, 150ms);
60
- }
61
-
62
- .stuic-comment-input-editor:focus-within {
63
- border-color: var(
64
- --stuic-comment-input-border-focus,
65
- var(--stuic-input-border-focus, currentColor)
66
- );
67
47
  }
68
48
 
69
49
  .stuic-comment-input.disabled {
@@ -71,196 +51,12 @@
71
51
  pointer-events: none;
72
52
  }
73
53
 
74
- /* --- Tab bar -------------------------------------------------------------- */
75
- .stuic-comment-input-bar {
76
- display: flex;
77
- align-items: center;
78
- justify-content: space-between;
79
- gap: 0.5rem;
80
- padding: 0.25rem 0.375rem;
81
- background: var(--stuic-comment-input-bar-bg, transparent);
82
- }
83
-
84
- .stuic-comment-input-tabs {
85
- display: flex;
86
- align-items: center;
87
- gap: 0.125rem;
88
- }
89
-
90
- .stuic-comment-input-tabs button {
91
- display: inline-flex;
92
- align-items: center;
93
- justify-content: center;
94
- height: 2rem;
95
- padding: 0 0.625rem;
96
- font-size: 0.8125rem;
97
- line-height: 1;
98
- color: inherit;
99
- opacity: 0.7;
100
- background: transparent;
101
- border: none;
102
- border-radius: var(--stuic-radius, 0.375rem);
103
- transition:
104
- background-color var(--stuic-transition, 150ms),
105
- opacity var(--stuic-transition, 150ms);
106
- }
107
-
108
- .stuic-comment-input-tabs button:hover:not(:disabled) {
109
- background: var(--stuic-comment-input-tab-hover-bg, rgb(0 0 0 / 0.06));
110
- }
111
-
112
- .stuic-comment-input-tabs button[data-active="true"] {
113
- opacity: 1;
114
- font-weight: 600;
115
- background: var(--stuic-comment-input-tab-active-bg, rgb(0 0 0 / 0.06));
116
- }
117
-
118
- .stuic-comment-input-tabs button:disabled {
119
- opacity: 0.4;
120
- }
121
-
122
- .stuic-comment-input-hint {
123
- font-size: 0.75rem;
124
- opacity: 0.5;
125
- padding-right: 0.25rem;
126
- }
127
-
128
- :root.dark .stuic-comment-input-tabs button:hover:not(:disabled),
129
- :root.dark .stuic-comment-input-tabs button[data-active="true"] {
130
- background: var(--stuic-comment-input-tab-active-bg, rgb(255 255 255 / 0.08));
131
- }
132
-
133
- @media (pointer: coarse) {
134
- .stuic-comment-input-tabs button {
135
- height: 2.5rem;
136
- }
137
- }
138
-
139
- /* --- Surface (textarea + preview share the same box) ---------------------- */
140
- .stuic-comment-input-surface {
141
- min-width: 0;
142
- }
143
-
144
- /* The inactive tab panel is removed via the `hidden` attribute; the explicit
145
- rule guarantees it collapses regardless of any utility-layer display. The
146
- write panel stays in the DOM while hidden so its textarea still submits and
147
- validates. */
148
- .stuic-comment-input-panel[hidden] {
149
- display: none;
150
- }
151
-
152
- .stuic-comment-input-textarea {
153
- display: block;
154
- width: 100%;
155
- min-height: var(--_min-height);
156
- max-width: 100%;
157
- margin: 0;
158
- padding: var(--_pad-y) var(--_pad-x);
159
- font-size: var(--_font-size);
160
- line-height: 1.6;
161
- color: inherit;
162
- background: transparent;
163
- border: none;
164
- outline: none;
165
- box-shadow: none;
166
- resize: none;
167
- }
168
-
169
- /* --- Preview -------------------------------------------------------------- */
170
- .stuic-comment-input-preview {
171
- min-height: var(--_min-height);
172
- padding: var(--_pad-y) var(--_pad-x);
173
- font-size: var(--_font-size);
174
- line-height: 1.6;
175
- overflow-wrap: break-word;
176
- }
177
-
178
- .stuic-comment-input-preview-empty {
179
- opacity: 0.5;
180
- font-style: italic;
181
- }
182
-
183
- .stuic-comment-input-preview > * + * {
184
- margin-top: 0.75em;
185
- }
186
- .stuic-comment-input-preview h1 {
187
- font-size: 1.5em;
188
- font-weight: 700;
189
- line-height: 1.2;
190
- }
191
- .stuic-comment-input-preview h2 {
192
- font-size: 1.3em;
193
- font-weight: 700;
194
- line-height: 1.25;
195
- }
196
- .stuic-comment-input-preview h3 {
197
- font-size: 1.15em;
198
- font-weight: 600;
199
- }
200
- .stuic-comment-input-preview ul,
201
- .stuic-comment-input-preview ol {
202
- padding-left: 1.5em;
203
- }
204
- .stuic-comment-input-preview ul {
205
- list-style: disc;
206
- }
207
- .stuic-comment-input-preview ol {
208
- list-style: decimal;
209
- }
210
- .stuic-comment-input-preview a {
211
- text-decoration: underline;
212
- }
213
- .stuic-comment-input-preview blockquote {
214
- padding-left: 1em;
215
- border-left: 3px solid var(--_border-color);
216
- opacity: 0.85;
217
- }
218
- .stuic-comment-input-preview code {
219
- font-family: var(
220
- --stuic-comment-input-font-mono,
221
- ui-monospace,
222
- SFMono-Regular,
223
- Menlo,
224
- monospace
225
- );
226
- font-size: 0.9em;
227
- padding: 0.1em 0.3em;
228
- border-radius: 0.25rem;
229
- background: var(--stuic-comment-input-code-bg, rgb(0 0 0 / 0.06));
230
- }
231
- .stuic-comment-input-preview pre {
232
- font-family: var(
233
- --stuic-comment-input-font-mono,
234
- ui-monospace,
235
- SFMono-Regular,
236
- Menlo,
237
- monospace
238
- );
239
- padding: 0.75em 1em;
240
- border-radius: var(--stuic-radius, 0.375rem);
241
- background: var(--stuic-comment-input-code-bg, rgb(0 0 0 / 0.06));
242
- overflow-x: auto;
243
- }
244
- .stuic-comment-input-preview pre code {
245
- padding: 0;
246
- background: transparent;
247
- }
248
- .stuic-comment-input-preview hr {
249
- border: none;
250
- border-top: var(--stuic-border-width, 1px) solid var(--_border-color);
251
- }
252
- .stuic-comment-input-preview table {
253
- border-collapse: collapse;
254
- }
255
- .stuic-comment-input-preview th,
256
- .stuic-comment-input-preview td {
257
- border: var(--stuic-border-width, 1px) solid var(--_border-color);
258
- padding: 0.25em 0.5em;
259
- }
260
-
261
- :root.dark .stuic-comment-input-preview code,
262
- :root.dark .stuic-comment-input-preview pre {
263
- background: var(--stuic-comment-input-code-bg, rgb(255 255 255 / 0.08));
54
+ /* The embedded MarkdownEditor wraps itself in InputWrap, which adds a bottom
55
+ margin meant for standalone fields. Inside the composer we want the footer to
56
+ sit directly beneath the editor, so collapse that margin — the footer adds its
57
+ own small top gap instead. */
58
+ .stuic-comment-input-main > .stuic-input {
59
+ margin-bottom: 0;
264
60
  }
265
61
 
266
62
  /* --- Footer (button row) -------------------------------------------------- */
@@ -269,7 +65,7 @@
269
65
  align-items: center;
270
66
  justify-content: space-between;
271
67
  gap: 0.5rem;
272
- padding: 0.5rem;
68
+ padding-top: var(--stuic-comment-input-footer-gap, 0.5rem);
273
69
  }
274
70
 
275
71
  .stuic-comment-input-footer-start,
@@ -282,29 +78,3 @@
282
78
  .stuic-comment-input-footer-end {
283
79
  margin-left: auto;
284
80
  }
285
-
286
- /* --- Size variants -------------------------------------------------------- */
287
- .stuic-comment-input[data-size="sm"] {
288
- --_pad-x: var(--stuic-comment-input-padding-x, var(--stuic-input-padding-x-sm, 0.5rem));
289
- --_pad-y: var(
290
- --stuic-comment-input-padding-y,
291
- var(--stuic-input-padding-y-sm, 0.375rem)
292
- );
293
- --_font-size: var(
294
- --stuic-comment-input-font-size,
295
- var(--stuic-input-font-size-sm, 0.8125rem)
296
- );
297
- --_min-height: var(--stuic-comment-input-min-height, 4rem);
298
- }
299
- .stuic-comment-input[data-size="lg"] {
300
- --_pad-x: var(--stuic-comment-input-padding-x, var(--stuic-input-padding-x-lg, 1rem));
301
- --_pad-y: var(
302
- --stuic-comment-input-padding-y,
303
- var(--stuic-input-padding-y-lg, 0.75rem)
304
- );
305
- --_font-size: var(
306
- --stuic-comment-input-font-size,
307
- var(--stuic-input-font-size-lg, 1.0625rem)
308
- );
309
- --_min-height: var(--stuic-comment-input-min-height, 7rem);
310
- }
@@ -1,2 +1 @@
1
- export { default as CommentInput, type Props as CommentInputProps, type CommentInputMode, } from "./CommentInput.svelte";
2
- export type { MarkdownRenderer } from "./_internal/render.js";
1
+ export { default as CommentInput, type Props as CommentInputProps, type CommentInputMode, DEFAULT_COMMENT_TOOLBAR, } from "./CommentInput.svelte";
@@ -1 +1 @@
1
- export { default as CommentInput, } from "./CommentInput.svelte";
1
+ export { default as CommentInput, DEFAULT_COMMENT_TOOLBAR, } from "./CommentInput.svelte";
@@ -529,6 +529,16 @@
529
529
  export function toggleMode(): void {
530
530
  toggleModeInternal();
531
531
  }
532
+ /**
533
+ * Imperatively run a toolbar command — same effect as clicking the matching
534
+ * toolbar button. Useful for wiring keyboard shortcuts (e.g. ⌘/Ctrl+B) from a
535
+ * host component. No-op while the backend is (re)loading. The `"|"` separator
536
+ * is not a command.
537
+ */
538
+ export function command(item: Exclude<ToolbarItem, "|">): void {
539
+ const btn = TOOLBAR_REGISTRY[item];
540
+ if (btn && activeHandle) btn.run(activeHandle.commands);
541
+ }
532
542
  </script>
533
543
 
534
544
  <InputWrap
@@ -99,6 +99,7 @@ declare const MarkdownEditor: import("svelte").Component<Props, {
99
99
  scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
100
100
  getMarkdown: () => string;
101
101
  toggleMode: () => void;
102
+ command: (item: Exclude<ToolbarItem, "|">) => void;
102
103
  }, "value" | "input" | "mode">;
103
104
  type MarkdownEditor = ReturnType<typeof MarkdownEditor>;
104
105
  export default MarkdownEditor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.135.0",
3
+ "version": "3.136.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -1,24 +0,0 @@
1
- /**
2
- * Default Preview renderer for {@link CommentInput}: turns a markdown string into
3
- * **sanitized** HTML.
4
- *
5
- * `marked` and `dompurify` are declared as *optional* peer dependencies and are
6
- * imported dynamically the first time a Preview tab is opened — consumers who
7
- * never preview (or who pass their own `renderMarkdown`) don't pay for them, and
8
- * a consumer who hasn't installed them gets a caught error (handled by the
9
- * component) rather than a hard build failure. Mirrors the lazy-load convention
10
- * used by `MarkdownEditor` for its Milkdown/CodeMirror backends.
11
- *
12
- * Sanitization is non-negotiable here: comment text is untrusted user content and
13
- * `marked` emits raw HTML embedded in markdown verbatim (an XSS vector). Every
14
- * code path that returns HTML runs it through DOMPurify first.
15
- */
16
- /** A markdown-to-HTML renderer. Return value may be sync or async. */
17
- export type MarkdownRenderer = (markdown: string) => string | Promise<string>;
18
- /**
19
- * Render markdown to sanitized HTML using the bundled (lazy) marked + DOMPurify
20
- * pipeline. Throws if the optional peers aren't installed (the caller catches
21
- * this and shows a fallback). SSR-safe: returns an empty string off the DOM,
22
- * since DOMPurify needs a `window` to sanitize against.
23
- */
24
- export declare function renderMarkdownSafe(markdown: string): Promise<string>;
@@ -1,47 +0,0 @@
1
- /**
2
- * Default Preview renderer for {@link CommentInput}: turns a markdown string into
3
- * **sanitized** HTML.
4
- *
5
- * `marked` and `dompurify` are declared as *optional* peer dependencies and are
6
- * imported dynamically the first time a Preview tab is opened — consumers who
7
- * never preview (or who pass their own `renderMarkdown`) don't pay for them, and
8
- * a consumer who hasn't installed them gets a caught error (handled by the
9
- * component) rather than a hard build failure. Mirrors the lazy-load convention
10
- * used by `MarkdownEditor` for its Milkdown/CodeMirror backends.
11
- *
12
- * Sanitization is non-negotiable here: comment text is untrusted user content and
13
- * `marked` emits raw HTML embedded in markdown verbatim (an XSS vector). Every
14
- * code path that returns HTML runs it through DOMPurify first.
15
- */
16
- // Cache the resolved (marked + DOMPurify) pipeline so the dynamic imports and
17
- // DOMPurify instance creation happen at most once per page.
18
- let cached = null;
19
- async function load() {
20
- if (cached)
21
- return cached;
22
- const [{ marked }, dompurify] = await Promise.all([
23
- import("marked"),
24
- import("dompurify"),
25
- ]);
26
- // DOMPurify's default export is the instance in a browser/DOM context.
27
- const DOMPurify = dompurify.default ?? dompurify;
28
- cached = async (markdown) => {
29
- // GitHub-flavored markdown + soft line breaks → <br>, matching how comment
30
- // boxes (incl. GitHub's) treat single newlines.
31
- const html = await marked.parse(markdown ?? "", { gfm: true, breaks: true });
32
- return DOMPurify.sanitize(html);
33
- };
34
- return cached;
35
- }
36
- /**
37
- * Render markdown to sanitized HTML using the bundled (lazy) marked + DOMPurify
38
- * pipeline. Throws if the optional peers aren't installed (the caller catches
39
- * this and shows a fallback). SSR-safe: returns an empty string off the DOM,
40
- * since DOMPurify needs a `window` to sanitize against.
41
- */
42
- export async function renderMarkdownSafe(markdown) {
43
- if (typeof window === "undefined")
44
- return "";
45
- const render = await load();
46
- return render(markdown);
47
- }