@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.
- package/AGENTS.md +2 -2
- package/API.md +29 -0
- package/dist/components/CodeBlock/CodeBlock.fixture.svelte +57 -0
- package/dist/components/CodeBlock/CodeBlock.fixture.svelte.d.ts +10 -0
- package/dist/components/CodeBlock/CodeBlock.svelte +441 -0
- package/dist/components/CodeBlock/CodeBlock.svelte.d.ts +113 -0
- package/dist/components/CodeBlock/README.md +336 -0
- package/dist/components/CodeBlock/_internal/lines.d.ts +13 -0
- package/dist/components/CodeBlock/_internal/lines.js +37 -0
- package/dist/components/CodeBlock/_internal/normalize-code.d.ts +9 -0
- package/dist/components/CodeBlock/_internal/normalize-code.js +39 -0
- package/dist/components/CodeBlock/_internal/paint.d.ts +20 -0
- package/dist/components/CodeBlock/_internal/paint.js +81 -0
- package/dist/components/CodeBlock/highlight/http.d.ts +6 -0
- package/dist/components/CodeBlock/highlight/http.js +62 -0
- package/dist/components/CodeBlock/highlight/index.d.ts +14 -0
- package/dist/components/CodeBlock/highlight/index.js +29 -0
- package/dist/components/CodeBlock/highlight/json.d.ts +9 -0
- package/dist/components/CodeBlock/highlight/json.js +74 -0
- package/dist/components/CodeBlock/highlight/shell.d.ts +8 -0
- package/dist/components/CodeBlock/highlight/shell.js +230 -0
- package/dist/components/CodeBlock/highlight/types.d.ts +17 -0
- package/dist/components/CodeBlock/highlight/types.js +1 -0
- package/dist/components/CodeBlock/i18n-sk.d.ts +17 -0
- package/dist/components/CodeBlock/i18n-sk.js +22 -0
- package/dist/components/CodeBlock/i18n.d.ts +37 -0
- package/dist/components/CodeBlock/i18n.js +34 -0
- package/dist/components/CodeBlock/index.css +470 -0
- package/dist/components/CodeBlock/index.d.ts +4 -0
- package/dist/components/CodeBlock/index.js +4 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/docs/domains/components.md +59 -1
- 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
|
-
|
|
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`.
|