@marianmeres/stuic 3.191.0 → 3.192.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.
Files changed (35) hide show
  1. package/AGENTS.md +2 -2
  2. package/API.md +29 -0
  3. package/dist/components/CodeBlock/CodeBlock.fixture.svelte +57 -0
  4. package/dist/components/CodeBlock/CodeBlock.fixture.svelte.d.ts +10 -0
  5. package/dist/components/CodeBlock/CodeBlock.svelte +441 -0
  6. package/dist/components/CodeBlock/CodeBlock.svelte.d.ts +113 -0
  7. package/dist/components/CodeBlock/README.md +336 -0
  8. package/dist/components/CodeBlock/_internal/lines.d.ts +13 -0
  9. package/dist/components/CodeBlock/_internal/lines.js +37 -0
  10. package/dist/components/CodeBlock/_internal/normalize-code.d.ts +9 -0
  11. package/dist/components/CodeBlock/_internal/normalize-code.js +39 -0
  12. package/dist/components/CodeBlock/_internal/paint.d.ts +20 -0
  13. package/dist/components/CodeBlock/_internal/paint.js +81 -0
  14. package/dist/components/CodeBlock/highlight/http.d.ts +6 -0
  15. package/dist/components/CodeBlock/highlight/http.js +62 -0
  16. package/dist/components/CodeBlock/highlight/index.d.ts +14 -0
  17. package/dist/components/CodeBlock/highlight/index.js +29 -0
  18. package/dist/components/CodeBlock/highlight/json.d.ts +9 -0
  19. package/dist/components/CodeBlock/highlight/json.js +74 -0
  20. package/dist/components/CodeBlock/highlight/shell.d.ts +8 -0
  21. package/dist/components/CodeBlock/highlight/shell.js +230 -0
  22. package/dist/components/CodeBlock/highlight/types.d.ts +17 -0
  23. package/dist/components/CodeBlock/highlight/types.js +1 -0
  24. package/dist/components/CodeBlock/i18n-sk.d.ts +17 -0
  25. package/dist/components/CodeBlock/i18n-sk.js +22 -0
  26. package/dist/components/CodeBlock/i18n.d.ts +37 -0
  27. package/dist/components/CodeBlock/i18n.js +34 -0
  28. package/dist/components/CodeBlock/index.css +470 -0
  29. package/dist/components/CodeBlock/index.d.ts +4 -0
  30. package/dist/components/CodeBlock/index.js +4 -0
  31. package/dist/index.css +1 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +1 -0
  34. package/docs/domains/components.md +59 -1
  35. package/package.json +1 -1
@@ -0,0 +1,470 @@
1
+ /* ============================================================================
2
+ CODE BLOCK COMPONENT TOKENS
3
+ Override globally: :root { --stuic-code-block-header-bg: transparent; }
4
+ Override locally: <CodeBlock style="--stuic-code-block-max-height: 20rem;">
5
+ The copy button and the collapse toggle ARE Buttons — theme them via
6
+ --stuic-button-* or `copyButtonProps`.
7
+ ============================================================================ */
8
+
9
+ :root {
10
+ /* Box — radius and border width are read with a tier fallback at the usage site
11
+ (`--stuic-code-block-radius` → `--stuic-radius-container`,
12
+ `--stuic-code-block-border-width` → `--stuic-border-width`), never declared here. */
13
+ --stuic-code-block-bg: transparent;
14
+ --stuic-code-block-border-color: var(--stuic-color-border);
15
+
16
+ /* The rules under the header and above the footer. The component's own width (like
17
+ ListGroup), so a theme that zeroes box borders keeps them. */
18
+ --stuic-code-block-rule-color: var(--stuic-color-border);
19
+ --stuic-code-block-rule-width: 1px;
20
+
21
+ /* Code. `--stuic-code-block-text` is not declared: it falls back to the inherited
22
+ color at the usage site. */
23
+ --stuic-code-block-padding-x: 1rem;
24
+ --stuic-code-block-padding-y: 1rem;
25
+ --stuic-code-block-font-family: var(--font-mono, ui-monospace, monospace);
26
+ --stuic-code-block-font-size: var(--text-sm);
27
+ --stuic-code-block-line-height: 1.625;
28
+ --stuic-code-block-code-tab-size: 4;
29
+ --stuic-code-block-max-height: none;
30
+
31
+ /* Focus ring of a scrollable <pre> and of the tabs (drawn inset: the box clips) */
32
+ --stuic-code-block-ring-width: 2px;
33
+ --stuic-code-block-ring-color: var(--stuic-color-ring);
34
+
35
+ /* Header. `--stuic-code-block-header-padding-x` is deliberately NOT declared: it is
36
+ read with a `--stuic-code-block-padding-x` fallback at the usage site, so the label
37
+ and the code start at the same x, and a scoped override of the code padding
38
+ reaches the header too. */
39
+ --stuic-code-block-header-bg: var(--stuic-color-surface);
40
+ --stuic-code-block-header-padding-y: 0.25rem;
41
+ --stuic-code-block-header-gap: 0.5rem;
42
+ /* surface-foreground, NOT muted-foreground: the header sits on `surface`, and
43
+ muted-foreground on surface falls below 4.5:1 in 54/54 light and 51/54 dark themes
44
+ (surface-foreground: min 7.3:1 light, 5.7:1 dark). Same for the tabs. */
45
+ --stuic-code-block-title-text: var(--stuic-color-surface-foreground);
46
+
47
+ /* Tabs (`samples`). The active one is marked by an indicator bar and weight, not by
48
+ dimming the others — a dimmed surface-foreground drops below 4.5:1. */
49
+ --stuic-code-block-tab-text: var(--stuic-color-surface-foreground);
50
+ --stuic-code-block-tab-padding-x: 0.75rem;
51
+ --stuic-code-block-tab-bg-hover: color-mix(
52
+ in srgb,
53
+ var(--stuic-color-surface-foreground) 8%,
54
+ transparent
55
+ );
56
+ --stuic-code-block-tab-font-weight-active: var(--font-weight-semibold, 600);
57
+ --stuic-code-block-tab-indicator-color: var(--stuic-color-surface-foreground);
58
+ --stuic-code-block-tab-indicator-width: 2px;
59
+
60
+ /* Lines (`lineNumbers`, `highlightLines`). The number color is 65% foreground over
61
+ the background: >= 4.68:1 in every bundled theme (the plain muted-foreground is
62
+ below 4.5:1 in 12 light ones). */
63
+ --stuic-code-block-line-number-text: color-mix(
64
+ in srgb,
65
+ var(--stuic-color-foreground) 65%,
66
+ var(--stuic-color-background)
67
+ );
68
+ --stuic-code-block-line-number-gap: 1.25rem;
69
+ --stuic-code-block-line-bg-highlighted: color-mix(
70
+ in srgb,
71
+ var(--stuic-color-primary) 12%,
72
+ transparent
73
+ );
74
+ --stuic-code-block-line-marker-color: var(--stuic-color-primary);
75
+ --stuic-code-block-line-marker-width: 3px;
76
+
77
+ /* Collapse (`collapsedLines`): the height of the fade over the cut-off */
78
+ --stuic-code-block-fade-size: 2lh;
79
+
80
+ /* Syntax colors — a GitHub-like palette, tuned so every color keeps >= 4.5:1 on the
81
+ background of every bundled theme, light and dark. `punctuation` is tokenized but
82
+ left in the text color (style `::highlight(stuic-code-block-punctuation)` to
83
+ change that). */
84
+ --stuic-code-block-token-comment-text: #57606a;
85
+ --stuic-code-block-token-meta-text: #57606a;
86
+ --stuic-code-block-token-string-text: #0a3069;
87
+ --stuic-code-block-token-number-text: #0550ae;
88
+ --stuic-code-block-token-literal-text: #0550ae;
89
+ --stuic-code-block-token-parameter-text: #0550ae;
90
+ --stuic-code-block-token-keyword-text: #cf222e;
91
+ --stuic-code-block-token-operator-text: #cf222e;
92
+ --stuic-code-block-token-property-text: #116329;
93
+ --stuic-code-block-token-variable-text: #953800;
94
+ --stuic-code-block-token-function-text: #6639ba;
95
+ }
96
+
97
+ :root.dark {
98
+ --stuic-code-block-token-comment-text: #8b949e;
99
+ --stuic-code-block-token-meta-text: #8b949e;
100
+ --stuic-code-block-token-string-text: #a5d6ff;
101
+ --stuic-code-block-token-number-text: #79c0ff;
102
+ --stuic-code-block-token-literal-text: #79c0ff;
103
+ --stuic-code-block-token-parameter-text: #79c0ff;
104
+ --stuic-code-block-token-keyword-text: #ff7b72;
105
+ --stuic-code-block-token-operator-text: #ff7b72;
106
+ --stuic-code-block-token-property-text: #7ee787;
107
+ --stuic-code-block-token-variable-text: #ffa657;
108
+ --stuic-code-block-token-function-text: #d2a8ff;
109
+ }
110
+
111
+ @layer components {
112
+ /* ============================================================================
113
+ BOX
114
+ ============================================================================ */
115
+
116
+ .stuic-code-block {
117
+ min-width: 0;
118
+ background: var(--stuic-code-block-bg);
119
+ border: var(--stuic-code-block-border-width, var(--stuic-border-width)) solid
120
+ var(--stuic-code-block-border-color);
121
+ border-radius: var(--stuic-code-block-radius, var(--stuic-radius-container));
122
+ /* The header background must follow the rounded corners. `clip`, not `hidden`: it
123
+ creates no scroll container (`hidden` stays as the fallback for browsers
124
+ without `clip`). The <pre> scrolls on its own. */
125
+ overflow: hidden;
126
+ overflow: clip;
127
+ }
128
+
129
+ /* ============================================================================
130
+ HEADER
131
+ ============================================================================ */
132
+
133
+ .stuic-code-block-header {
134
+ display: flex;
135
+ /* wraps only where a query below asks for it — every part's basis is 0 or fits */
136
+ flex-wrap: wrap;
137
+ align-items: center;
138
+ gap: var(--stuic-code-block-header-gap);
139
+ /* the header's actual block padding — the tabs' negative margins follow it */
140
+ --_header-padding-y: var(--stuic-code-block-header-padding-y);
141
+ padding-block: var(--_header-padding-y);
142
+ padding-inline: var(
143
+ --stuic-code-block-header-padding-x,
144
+ var(--stuic-code-block-padding-x)
145
+ );
146
+ min-width: 0;
147
+ background: var(--stuic-code-block-header-bg);
148
+ border-bottom: var(--stuic-code-block-rule-width) solid
149
+ var(--stuic-code-block-rule-color);
150
+ /* its own width, not the page's, decides when a title gets its own row */
151
+ container: stuic-code-block-header / inline-size;
152
+ }
153
+
154
+ .stuic-code-block-title {
155
+ /* basis 0: a long file name truncates instead of pushing the button down */
156
+ flex: 1 1 0;
157
+ min-width: 0;
158
+ overflow: hidden;
159
+ text-overflow: ellipsis;
160
+ white-space: nowrap;
161
+ font-family: var(--stuic-code-block-font-family);
162
+ font-size: var(--stuic-code-block-font-size);
163
+ color: var(--stuic-code-block-title-text);
164
+ }
165
+
166
+ /* stays at the end with or without a label or tabs */
167
+ .stuic-code-block-copy {
168
+ margin-inline-start: auto;
169
+ flex: none;
170
+ }
171
+
172
+ /* The (borderless) copy button sits flush in the header's end corner: no padding
173
+ around it but on the start side, where the label or the tabs begin. Flush, it is
174
+ a square-cornered cell like a tab, and its focus ring is drawn inset (the box
175
+ clips). */
176
+ .stuic-code-block[data-copy="true"] .stuic-code-block-header {
177
+ --_header-padding-y: 0px;
178
+ padding-inline-end: 0;
179
+ }
180
+
181
+ .stuic-code-block[data-copy="true"] .stuic-code-block-copy {
182
+ border-radius: 0;
183
+ }
184
+
185
+ .stuic-code-block[data-copy="true"] .stuic-code-block-copy:focus-visible {
186
+ outline-offset: calc(-1 * var(--stuic-button-ring-width));
187
+ }
188
+
189
+ /* ============================================================================
190
+ TABS — they reach the header's top and bottom edges, so the active tab's
191
+ indicator sits on the header rule
192
+ ============================================================================ */
193
+
194
+ /* with tabs, the title keeps its width and the tabs take the rest */
195
+ .stuic-code-block[data-samples="true"] .stuic-code-block-title {
196
+ flex: 0 1 auto;
197
+ }
198
+
199
+ .stuic-code-block-tabs {
200
+ display: flex;
201
+ align-self: stretch;
202
+ flex: 1 1 0;
203
+ min-width: 0;
204
+ margin-block: calc(-1 * var(--_header-padding-y));
205
+ /* many tabs in a narrow block scroll sideways rather than wrap */
206
+ overflow-x: auto;
207
+ scrollbar-width: thin;
208
+ }
209
+
210
+ /* the first tab's text starts where a label would */
211
+ .stuic-code-block-tabs:first-child {
212
+ margin-inline-start: calc(-1 * var(--stuic-code-block-tab-padding-x));
213
+ }
214
+
215
+ /* narrow: a title over the tabs gets its own row, so the tabs keep the width */
216
+ @container stuic-code-block-header (max-width: 36rem) {
217
+ .stuic-code-block[data-samples="true"] .stuic-code-block-title {
218
+ flex-basis: 100%;
219
+ padding-block-start: var(--stuic-code-block-header-padding-y);
220
+ }
221
+ .stuic-code-block-title + .stuic-code-block-tabs {
222
+ /* a real basis, so the (negative-margined) tabs can't squeeze onto the title's
223
+ row and push the button down instead */
224
+ flex-basis: 50%;
225
+ margin-block-start: 0;
226
+ margin-inline-start: calc(-1 * var(--stuic-code-block-tab-padding-x));
227
+ }
228
+ }
229
+
230
+ .stuic-code-block-tab {
231
+ flex: none;
232
+ display: inline-flex;
233
+ align-items: center;
234
+ margin: 0;
235
+ padding-block: var(--stuic-code-block-header-padding-y);
236
+ padding-inline: var(--stuic-code-block-tab-padding-x);
237
+ font-family: var(--stuic-code-block-font-family);
238
+ font-size: var(--stuic-code-block-font-size);
239
+ color: var(--stuic-code-block-tab-text);
240
+ white-space: nowrap;
241
+ background: none;
242
+ border: 0;
243
+ border-radius: 0;
244
+ cursor: pointer;
245
+ -webkit-tap-highlight-color: transparent;
246
+ transition: background-color
247
+ var(--stuic-code-block-transition, var(--stuic-transition));
248
+ }
249
+
250
+ .stuic-code-block-tab:hover {
251
+ background: var(--stuic-code-block-tab-bg-hover);
252
+ }
253
+
254
+ .stuic-code-block-tab:focus-visible {
255
+ outline: var(--stuic-code-block-ring-width) solid var(--stuic-code-block-ring-color);
256
+ outline-offset: calc(-1 * var(--stuic-code-block-ring-width));
257
+ }
258
+
259
+ .stuic-code-block-tab[data-active="true"] {
260
+ font-weight: var(--stuic-code-block-tab-font-weight-active);
261
+ box-shadow: inset 0 calc(-1 * var(--stuic-code-block-tab-indicator-width)) 0
262
+ var(--stuic-code-block-tab-indicator-color);
263
+ }
264
+
265
+ /* ============================================================================
266
+ CODE
267
+ ============================================================================ */
268
+
269
+ .stuic-code-block-pre {
270
+ margin: 0;
271
+ padding-block: var(--stuic-code-block-padding-y);
272
+ padding-inline: var(--stuic-code-block-padding-x);
273
+ /* the max height is the box's, padding included — with or without preflight */
274
+ box-sizing: border-box;
275
+ max-height: var(--stuic-code-block-max-height);
276
+ overflow: auto;
277
+ font-family: var(--stuic-code-block-font-family);
278
+ font-size: var(--stuic-code-block-font-size);
279
+ line-height: var(--stuic-code-block-line-height);
280
+ tab-size: var(--stuic-code-block-code-tab-size);
281
+ /* `currentColor` for `color` is the inherited color */
282
+ color: var(--stuic-code-block-text, currentColor);
283
+ white-space: pre;
284
+ }
285
+
286
+ .stuic-code-block-pre:focus-visible {
287
+ outline: var(--stuic-code-block-ring-width) solid var(--stuic-code-block-ring-color);
288
+ outline-offset: calc(-1 * var(--stuic-code-block-ring-width));
289
+ }
290
+
291
+ .stuic-code-block[data-wrap="true"] .stuic-code-block-pre {
292
+ white-space: pre-wrap;
293
+ /* `anywhere`, not `break-word`: a long URL or token must not widen the box */
294
+ overflow-wrap: anywhere;
295
+ }
296
+
297
+ .stuic-code-block-code {
298
+ /* undo UA/preflight code styling — the <pre> carries the typography */
299
+ font: inherit;
300
+ color: inherit;
301
+ background: none;
302
+ padding: 0;
303
+ }
304
+
305
+ /* ============================================================================
306
+ LINES — one block <span> per line, each keeping its own "\n" (the text,
307
+ the selection and the copy stay the plain sample). The inline padding
308
+ moves from the <pre> to the lines so a highlighted line's background
309
+ reaches both edges; the <code> grows to the longest line so it does so
310
+ while scrolled too.
311
+ ============================================================================ */
312
+
313
+ .stuic-code-block[data-lines="true"] .stuic-code-block-pre {
314
+ padding-inline: 0;
315
+ }
316
+
317
+ .stuic-code-block[data-lines="true"] .stuic-code-block-code {
318
+ display: block;
319
+ width: max-content;
320
+ min-width: 100%;
321
+ }
322
+
323
+ .stuic-code-block[data-lines="true"][data-wrap="true"] .stuic-code-block-code {
324
+ width: auto;
325
+ }
326
+
327
+ .stuic-code-block-line {
328
+ display: block;
329
+ position: relative;
330
+ padding-inline: var(--stuic-code-block-padding-x);
331
+ }
332
+
333
+ .stuic-code-block[data-line-numbers="true"] .stuic-code-block-line {
334
+ /* a wrapped continuation stays in the code column */
335
+ padding-inline-start: calc(
336
+ var(--stuic-code-block-padding-x) + var(--_digits, 2) * 1ch +
337
+ var(--stuic-code-block-line-number-gap)
338
+ );
339
+ }
340
+
341
+ .stuic-code-block[data-line-numbers="true"] .stuic-code-block-line::before {
342
+ content: attr(data-line);
343
+ position: absolute;
344
+ inset-inline-start: var(--stuic-code-block-padding-x);
345
+ width: calc(var(--_digits, 2) * 1ch);
346
+ text-align: end;
347
+ font-variant-numeric: tabular-nums;
348
+ color: var(--stuic-code-block-line-number-text);
349
+ /* never selected, never copied */
350
+ user-select: none;
351
+ pointer-events: none;
352
+ }
353
+
354
+ .stuic-code-block-line[data-highlighted="true"] {
355
+ background: var(--stuic-code-block-line-bg-highlighted);
356
+ box-shadow: inset var(--stuic-code-block-line-marker-width) 0 0
357
+ var(--stuic-code-block-line-marker-color);
358
+ }
359
+
360
+ /* ============================================================================
361
+ COLLAPSED (`collapsedLines`) — N lines tall, padding included; the rest
362
+ is hidden behind a fade and the footer toggle
363
+ ============================================================================ */
364
+
365
+ .stuic-code-block[data-collapsed="true"] .stuic-code-block-pre {
366
+ max-height: calc(
367
+ var(--_collapsed-lines) * 1lh + 2 * var(--stuic-code-block-padding-y)
368
+ );
369
+ overflow-y: hidden;
370
+ mask-image: linear-gradient(
371
+ to bottom,
372
+ #000 calc(100% - var(--stuic-code-block-fade-size)),
373
+ transparent
374
+ );
375
+ }
376
+
377
+ .stuic-code-block-footer {
378
+ display: flex;
379
+ border-top: var(--stuic-code-block-rule-width) solid
380
+ var(--stuic-code-block-rule-color);
381
+ }
382
+
383
+ .stuic-code-block .stuic-code-block-toggle {
384
+ flex: 1 1 auto;
385
+ border-radius: 0;
386
+ font-size: var(--stuic-code-block-font-size);
387
+ }
388
+
389
+ .stuic-code-block-toggle-icon {
390
+ display: inline-flex;
391
+ transition: transform var(--stuic-code-block-transition, var(--stuic-transition));
392
+ }
393
+
394
+ .stuic-code-block-toggle-icon svg {
395
+ width: 1.25em;
396
+ height: 1.25em;
397
+ }
398
+
399
+ .stuic-code-block-toggle[aria-expanded="true"] .stuic-code-block-toggle-icon {
400
+ transform: rotate(180deg);
401
+ }
402
+
403
+ @media (prefers-reduced-motion: reduce) {
404
+ .stuic-code-block-toggle-icon {
405
+ transition: none;
406
+ }
407
+ }
408
+
409
+ /* ============================================================================
410
+ SYNTAX COLORS — the CSS Custom Highlight API. The descendant form is
411
+ needed: Firefox styles a highlight by the element that holds the text,
412
+ which is a line <span> in the lined layout.
413
+ ============================================================================ */
414
+
415
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
416
+ stuic-code-block-comment
417
+ ) {
418
+ color: var(--stuic-code-block-token-comment-text);
419
+ }
420
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
421
+ stuic-code-block-meta
422
+ ) {
423
+ color: var(--stuic-code-block-token-meta-text);
424
+ }
425
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
426
+ stuic-code-block-string
427
+ ) {
428
+ color: var(--stuic-code-block-token-string-text);
429
+ }
430
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
431
+ stuic-code-block-number
432
+ ) {
433
+ color: var(--stuic-code-block-token-number-text);
434
+ }
435
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
436
+ stuic-code-block-literal
437
+ ) {
438
+ color: var(--stuic-code-block-token-literal-text);
439
+ }
440
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
441
+ stuic-code-block-parameter
442
+ ) {
443
+ color: var(--stuic-code-block-token-parameter-text);
444
+ }
445
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
446
+ stuic-code-block-keyword
447
+ ) {
448
+ color: var(--stuic-code-block-token-keyword-text);
449
+ }
450
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
451
+ stuic-code-block-operator
452
+ ) {
453
+ color: var(--stuic-code-block-token-operator-text);
454
+ }
455
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
456
+ stuic-code-block-property
457
+ ) {
458
+ color: var(--stuic-code-block-token-property-text);
459
+ }
460
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
461
+ stuic-code-block-variable
462
+ ) {
463
+ color: var(--stuic-code-block-token-variable-text);
464
+ }
465
+ :is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(
466
+ stuic-code-block-function
467
+ ) {
468
+ color: var(--stuic-code-block-token-function-text);
469
+ }
470
+ }
@@ -0,0 +1,4 @@
1
+ export { default as CodeBlock, type Props as CodeBlockProps, type CodeBlockSample, } from "./CodeBlock.svelte";
2
+ export { highlightCode, highlightJson, highlightHttp, highlightShell, HIGHLIGHT_CODE_LANGS, type CodeBlockHighlighter, type CodeBlockToken, type CodeBlockTokenType, } from "./highlight/index.js";
3
+ export { createCodeBlockT, CODE_BLOCK_MESSAGES_EN, type CodeBlockMessageKey, type CodeBlockMessages, } from "./i18n.js";
4
+ export { CODE_BLOCK_MESSAGES_SK } from "./i18n-sk.js";
@@ -0,0 +1,4 @@
1
+ export { default as CodeBlock, } from "./CodeBlock.svelte";
2
+ export { highlightCode, highlightJson, highlightHttp, highlightShell, HIGHLIGHT_CODE_LANGS, } from "./highlight/index.js";
3
+ export { createCodeBlockT, CODE_BLOCK_MESSAGES_EN, } from "./i18n.js";
4
+ export { CODE_BLOCK_MESSAGES_SK } from "./i18n-sk.js";
package/dist/index.css CHANGED
@@ -86,6 +86,7 @@ In practice:
86
86
  @import "./components/RegisterForm/index.css";
87
87
  @import "./components/LoginOrRegisterForm/index.css";
88
88
  @import "./components/Checkout/index.css";
89
+ @import "./components/CodeBlock/index.css";
89
90
  @import "./components/ColorPicker/index.css";
90
91
  @import "./components/CommandMenu/index.css";
91
92
  /* NOTE: CommentInput/index.css is deliberately NOT imported here — same reason as
package/dist/index.d.ts CHANGED
@@ -38,6 +38,7 @@ export * from "./components/Card/index.js";
38
38
  export * from "./components/Carousel/index.js";
39
39
  export * from "./components/Checkout/index.js";
40
40
  export * from "./components/Circle/index.js";
41
+ export * from "./components/CodeBlock/index.js";
41
42
  export * from "./components/Collapsible/index.js";
42
43
  export * from "./components/ColorPicker/index.js";
43
44
  export * from "./components/ColorScheme/index.js";
package/dist/index.js CHANGED
@@ -39,6 +39,7 @@ export * from "./components/Card/index.js";
39
39
  export * from "./components/Carousel/index.js";
40
40
  export * from "./components/Checkout/index.js";
41
41
  export * from "./components/Circle/index.js";
42
+ export * from "./components/CodeBlock/index.js";
42
43
  export * from "./components/Collapsible/index.js";
43
44
  export * from "./components/ColorPicker/index.js";
44
45
  export * from "./components/ColorScheme/index.js";
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- 82 Svelte 5 component directories with consistent API patterns. All use runes-based reactivity.
5
+ 83 Svelte 5 component directories with consistent API patterns. All use runes-based reactivity.
6
6
 
7
7
  ## Component Categories
8
8
 
@@ -92,6 +92,7 @@
92
92
  | UserAvatarMenu | Avatar trigger + dropdown menu with header tile, color-scheme toggle, authed/unauthed states |
93
93
  | Pill | Inline rounded badge/tag/chip (intent + variant + size, dismissible, dot, polymorphic span/a/button) |
94
94
  | KbdShortcut | Keyboard shortcut hints |
95
+ | CodeBlock | Copyable code sample: label/tabs + copy, JSON/HTTP/shell highlighting, line numbers, collapse |
95
96
  | Carousel | Image/content slider with snap, keyboard nav, wheel scroll, arrows |
96
97
  | ListItemButton | List item with actions |
97
98
  | AnimatedElipsis | Loading dots animation |
@@ -1089,6 +1090,63 @@ Prefix: `--stuic-description-list-*`
1089
1090
 
1090
1091
  ---
1091
1092
 
1093
+ ## CodeBlock
1094
+
1095
+ A copyable code sample for developer docs: a bordered, rounded box with a header (the language or a file name — or a tab list for several samples — on the start side, a borderless `CopyButton` on the end side) over a `<pre><code>`, and a footer toggle when collapsed. Renders `<div>` › optional header `<div>` › `<pre>` › `<code>` › optional footer `<div>`. The code is rendered as text, and the button copies exactly what is shown.
1096
+
1097
+ ### Exports
1098
+
1099
+ | Export | Kind | Description |
1100
+ | -------------------------------------------------------------- | --------- | ---------------------------------------------------------------------------------- |
1101
+ | `CodeBlock` | component | Main component |
1102
+ | `CodeBlockProps`, `CodeBlockSample` | type | Props; one tab's sample `{ code, lang?, label?, id?, highlightLines?, copyText? }` |
1103
+ | `highlightCode` | function | The default highlighter: dispatches JSON / HTTP / shell by `lang` |
1104
+ | `highlightJson`, `highlightHttp`, `highlightShell` | function | The tokenizers, `(code) => CodeBlockToken[]` |
1105
+ | `HIGHLIGHT_CODE_LANGS` | const | The `lang`s `highlightCode` knows |
1106
+ | `CodeBlockToken`, `CodeBlockTokenType`, `CodeBlockHighlighter` | type | `[start, end, type]`; the 12 built-in types; `(code, lang?) => tokens` |
1107
+ | `createCodeBlockT`, `CODE_BLOCK_MESSAGES_EN/_SK` | i18n | Catalog includes the `CopyButton` keys + `show_all_lines`, `show_less` |
1108
+
1109
+ ### Key Props
1110
+
1111
+ | Prop | Type | Default | Description |
1112
+ | ------------------ | --------------------------------- | ------- | --------------------------------------------------------------------------------------------- |
1113
+ | `code` | `string` | — | The sample (ignored with `samples`) |
1114
+ | `lang` | `string` | — | Header label (unless `title`), highlighter input, `data-lang`, `language-{lang}` on `<code>` |
1115
+ | `title` | `THC` | — | Header label in place of `lang`; `""` hides it; with `samples` it precedes and names the tabs |
1116
+ | `samples` | `CodeBlockSample[]` | — | Tabs; `bind:active` (the sample id: `id` ?? string `label` ?? `lang` ?? index) |
1117
+ | `highlight` | `boolean \| CodeBlockHighlighter` | `true` | Syntax highlighting |
1118
+ | `lineNumbers` | `boolean` | `false` | `lineNumbersStart` offsets the numbering |
1119
+ | `highlightLines` | `number[] \| string` | — | 1-based positions in the sample, `"1, 3-5"` |
1120
+ | `collapsedLines` | `number` | — | Collapse to N lines + toggle; `bind:expanded` |
1121
+ | `verbatim`, `wrap` | `boolean` | `false` | Skip normalization; soft-wrap |
1122
+ | `copy` | `boolean` | `true` | Render the copy button; with no label or tabs either, the header is not rendered |
1123
+ | `copyButtonProps` | `Partial<CopyButtonProps>` | — | Spread onto the `CopyButton` after the defaults (`label`, `ghost`, `sm`); `text` overrides |
1124
+ | `t` | `TranslateFn` | English | Passed to the `CopyButton` too |
1125
+
1126
+ Class slots: `class`, `classHeader`, `classTitle`, `classTabs`, `classTab`, `classPre`, `classCode`, `classLine`, `classFooter`, `classToggle` (the copy button's via `copyButtonProps.class`, merged). Rest props go to the root `<div>`.
1127
+
1128
+ ### Deliberate choices
1129
+
1130
+ - **Highlighting via the CSS Custom Highlight API** (`_internal/paint.ts`): tokens become `Range`s in shared named `Highlight`s (`stuic-code-block-<type>`), styled by `:is(.stuic-code-block-code, .stuic-code-block-code *)::highlight(...)` — the descendant form because Firefox styles by the element holding the text. No markup: text, selection, copy and SSR are the plain sample; unsupported browsers show plain text. Each block removes exactly its ranges on cleanup. The paint `$effect` depends on `text`, `lang`, the highlighter and `lined` (the DOM the ranges point into), and refuses to paint when `textContent !== text`. Verified in Chromium, WebKit and Firefox, including scoped `var()` token overrides.
1131
+ - **Tokenizers** (`highlight/*.ts`, node-tested) are tolerant scanners, not parsers: never throw, emit ordered non-overlapping tokens. A throwing custom highlighter is caught and logged.
1132
+ - **Lines** render only with `lineNumbers`/`highlightLines`: a block `<span>` per line that keeps its `\n`, so `textContent`, a native copy (checked in all three engines) and the token offsets stay the sample. Numbers are `::before { content: attr(data-line) }` with `user-select: none`. With lines, the `<pre>`'s inline padding moves to the lines and the `<code>` is `width: max-content; min-width: 100%`, so a highlighted band spans the scrolled width.
1133
+ - **Collapse** clamps the `<pre>` to `calc(N * 1lh + 2 * padding-y)` with `overflow-y: hidden` and a `mask-image` fade; the hidden overflow doesn't count toward the measured tab stop. Collapsing back scrolls the block into view if its top is above the viewport.
1134
+ - **Tabs**: WAI-ARIA tabs (automatic activation, roving tabindex, ←/→ wrap, Home/End); the `<pre>` is the tab panel (always a tab stop). A block lacking the bound `active` id keeps its last sample and never writes the value, so several blocks can share one binding. The header is an `inline-size` container: below 36rem a title over tabs gets its own row.
1135
+ - **Flush copy button:** with `copy`, `data-copy` on the root drops the header's block and end padding (a private `--_header-padding-y` on the header, which the tabs' negative margins follow), and the button gets `border-radius: 0` and an inset focus ring — the box clips.
1136
+ - **Contrast** (measured over the 54 bundled themes): the label and tabs use `surface-foreground` on `surface` (min 7.3:1 light / 5.7:1 dark; `muted-foreground` fails 4.5:1 almost everywhere); inactive tabs are not dimmed; line numbers are 65% foreground over background (min 4.68:1); the GitHub-like syntax palette (switched under `:root.dark`) keeps ≥ 4.5:1 on every theme background.
1137
+ - **`not-prose` on the root** (styled mode only). No outer margin is declared.
1138
+ - **Normalization** (`_internal/normalize-code.ts`): CRLF → LF, blank end lines dropped, the common leading-whitespace _string_ removed.
1139
+ - **Measured tab stop** on the `<pre>` while it scrolls (`ResizeObserver` + explicit `text`/`wrap`/`lined`/`collapsed` dependencies). Hence the `svelte-ignore a11y_no_noninteractive_tabindex`.
1140
+ - `unstyled` propagates to the `CopyButton` and the toggle, and keeps the `language-*` class, roles and tab stops.
1141
+
1142
+ ### CSS Tokens
1143
+
1144
+ Prefix: `--stuic-code-block-*`
1145
+
1146
+ `bg`, `border-color`, `rule-color`, `rule-width`, `padding-x`, `padding-y`, `font-family`, `font-size` (`var(--text-sm)`), `line-height`, `code-tab-size`, `max-height`, `ring-width`, `ring-color`, `header-bg`, `header-padding-y`, `header-gap`, `title-text`, `tab-text`, `tab-padding-x`, `tab-bg-hover`, `tab-font-weight-active`, `tab-indicator-color`, `tab-indicator-width`, `line-number-text`, `line-number-gap`, `line-bg-highlighted`, `line-marker-color`, `line-marker-width`, `fade-size`, `token-{comment,meta,string,number,literal,parameter,keyword,operator,property,variable,function}-text`. Not declared (usage-site fallbacks): `radius` (→ `--stuic-radius-container`), `border-width` (→ `--stuic-border-width`), `transition` (→ `--stuic-transition`), `text` (→ `currentColor`), `header-padding-x` (→ `padding-x`).
1147
+
1148
+ ---
1149
+
1092
1150
  ## ListGroup
1093
1151
 
1094
1152
  A rounded, bordered box of rows split by hairlines — optional header (title start, figure end), optional footer line — the block back offices keep hand-rolling. Renders `<div>` › header `<div>` › `<ul role="list">` › `<li>` per row › footer `<div>`. Each row is **one wrapping flex line** of parts (a code, a name that takes the slack, a run of facts), not columns: nothing lines up across rows, and in a narrow drawer the trailing facts drop under the name. Not a `DataTable` (no column model), not a `DescriptionList` (many things, not properties of one), not a `Card` (no shadow, no padded body). Generic over `T`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.191.0",
3
+ "version": "3.192.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",