@marianmeres/stuic 3.190.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 (43) 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/components/RangeSlider/README.md +22 -22
  32. package/dist/components/RangeSlider/RangeSlider.svelte +4 -4
  33. package/dist/components/RangeSlider/RangeSlider.svelte.d.ts +2 -2
  34. package/dist/components/RangeSlider/index.css +9 -6
  35. package/dist/components/Slider/README.md +23 -24
  36. package/dist/components/Slider/Slider.svelte +4 -4
  37. package/dist/components/Slider/Slider.svelte.d.ts +2 -2
  38. package/dist/components/Slider/index.css +6 -3
  39. package/dist/index.css +1 -0
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +1 -0
  42. package/docs/domains/components.md +59 -1
  43. package/package.json +1 -1
@@ -22,7 +22,7 @@ variant for custom UI (volume/brightness controls, dashboards, media players).
22
22
  | `thumb` | `boolean \| Snippet<[SliderRenderCtx]>` | `true` | `false` hides the thumb (fill-only look), snippet renders inside thumb |
23
23
  | `thumbPosition` | `"value" \| "start"` | `"value"` | `"value"` rides the fill edge; `"start"` pins it to the left/bottom so only the bar moves (true iOS volume look) |
24
24
  | `thumbReserve` | `boolean` | `true` | Reserve the thumb's footprint along the track, so the fill never shrinks below the thumb. `false` = linear mapping across the full track (fill collapses to zero at `min`); only honored with `thumbPosition="start"` |
25
- | `fillRounded` | `boolean` | `false` | Round the fill's leading edge ("pill inside a pill") |
25
+ | `fillRounded` | `boolean` | `true` | Round the fill's leading edge ("pill inside a pill"); `false` cuts it flat |
26
26
  | `ticks` | `boolean \| number[]` | - | `true` = tick at every `step` (positive numeric step only; skipped above 101 auto ticks — pass an array), array = ticks at given in-range values |
27
27
  | `valueLabel` | `Snippet<[SliderRenderCtx]>` | - | Floating label at the current value along the track |
28
28
  | `disabled` | `boolean` | `false` | Disable interaction |
@@ -103,7 +103,6 @@ track and the fill collapses to zero at `min`.
103
103
  <Slider
104
104
  orientation="vertical"
105
105
  thumbPosition="start"
106
- fillRounded
107
106
  thumbClass="bg-transparent shadow-none"
108
107
  bind:value={volume}
109
108
  >
@@ -123,8 +122,8 @@ track and the fill collapses to zero at `min`.
123
122
  With `thumbPosition="start"` the thumb never moves — only the bar does. It is purely
124
123
  decorative (the full-size hidden input is always the topmost hit target, so the thumb
125
124
  never receives pointer events), but it still **reserves its footprint** along the track:
126
- the fill can never shrink below it, and at `min` it is exactly a thumb-sized nub — with
127
- `fillRounded`, a circle enclosing the pinned thumb, same as the default
125
+ the fill can never shrink below it, and at `min` it is exactly a thumb-sized nub — by
126
+ default (`fillRounded`) a circle enclosing the pinned thumb, same as the default
128
127
  `thumbPosition="value"` look.
129
128
 
130
129
  Opt out with `thumbReserve={false}` for a linear mapping across the full track, where
@@ -202,23 +201,23 @@ the fill collapses to zero at `min` (and, at low values, is shorter than the thu
202
201
 
203
202
  ## CSS Variables
204
203
 
205
- | Variable | Default | Description |
206
- | --------------------------------- | -------------------------- | --------------------------------- |
207
- | `--stuic-slider-track` | `--stuic-color-muted` | Track (pill background) color |
208
- | `--stuic-slider-fill` | `--stuic-color-primary` | Fill color |
209
- | `--stuic-slider-thumb` | `--color-white` | Thumb background |
210
- | `--stuic-slider-thumb-foreground` | `--stuic-color-foreground` | Thumb content color |
211
- | `--stuic-slider-tick` | foreground 25% mix | Tick mark color (over the track) |
212
- | `--stuic-slider-tick-on-fill` | background 55% mix | Tick mark color (over the fill) |
213
- | `--stuic-slider-ring-width` | `4px` | Focus ring width |
214
- | `--stuic-slider-ring-color` | `--stuic-color-ring` | Focus ring color |
215
- | `--stuic-slider-thickness` | `2rem` (`sm` 1.25, `lg` 3) | Cross-axis size |
216
- | `--stuic-slider-length` | `10rem` | Main-axis size |
217
- | `--stuic-slider-thumb-inset` | `3px` | Gap between thumb and track edge |
218
- | `--stuic-slider-radius` | `9999px` | Track corner radius |
219
- | `--stuic-slider-fill-radius` | `--stuic-slider-radius` | Fill radius (when `fillRounded`) |
220
- | `--stuic-slider-thumb-radius` | `9999px` | Thumb corner radius |
221
- | `--stuic-slider-thumb-shadow` | `--stuic-shadow` | Thumb shadow |
222
- | `--stuic-slider-tick-size` | `4px` | Tick mark diameter |
223
- | `--stuic-slider-value-gap` | `0.375rem` | Gap between track and value label |
224
- | `--stuic-slider-transition` | `--stuic-transition` | Fill/thumb movement transition |
204
+ | Variable | Default | Description |
205
+ | --------------------------------- | -------------------------- | ------------------------------------------ |
206
+ | `--stuic-slider-track` | `--stuic-color-muted` | Track (pill background) color |
207
+ | `--stuic-slider-fill` | `--stuic-color-primary` | Fill color |
208
+ | `--stuic-slider-thumb` | `--color-white` | Thumb background |
209
+ | `--stuic-slider-thumb-foreground` | `--stuic-color-foreground` | Thumb content color |
210
+ | `--stuic-slider-tick` | foreground 25% mix | Tick mark color (over the track) |
211
+ | `--stuic-slider-tick-on-fill` | background 55% mix | Tick mark color (over the fill) |
212
+ | `--stuic-slider-ring-width` | `4px` | Focus ring width |
213
+ | `--stuic-slider-ring-color` | `--stuic-color-ring` | Focus ring color |
214
+ | `--stuic-slider-thickness` | `2rem` (`sm` 1.25, `lg` 3) | Cross-axis size |
215
+ | `--stuic-slider-length` | `10rem` | Main-axis size |
216
+ | `--stuic-slider-thumb-inset` | `3px` | Gap between thumb and track edge |
217
+ | `--stuic-slider-radius` | `9999px` | Track corner radius |
218
+ | `--stuic-slider-fill-radius` | `--stuic-slider-radius` | Fill radius (unless `fillRounded={false}`) |
219
+ | `--stuic-slider-thumb-radius` | `9999px` | Thumb corner radius |
220
+ | `--stuic-slider-thumb-shadow` | `--stuic-shadow` | Thumb shadow |
221
+ | `--stuic-slider-tick-size` | `4px` | Tick mark diameter |
222
+ | `--stuic-slider-value-gap` | `0.375rem` | Gap between track and value label |
223
+ | `--stuic-slider-transition` | `--stuic-transition` | Fill/thumb movement transition |
@@ -100,8 +100,8 @@
100
100
  */
101
101
  thumbReserve?: boolean;
102
102
  /**
103
- * Round the fill's leading edge (instead of a flat cut), giving the
104
- * "pill inside a pill" look.
103
+ * Round the fill's leading edge, giving the "pill inside a pill" look
104
+ * (the default). `false` cuts it flat.
105
105
  */
106
106
  fillRounded?: boolean;
107
107
  /**
@@ -152,7 +152,7 @@
152
152
  thumb = true,
153
153
  thumbPosition = "value",
154
154
  thumbReserve = true,
155
- fillRounded = false,
155
+ fillRounded = true,
156
156
  ticks,
157
157
  valueLabel,
158
158
  el = $bindable(),
@@ -447,7 +447,7 @@
447
447
  data-thumb-position={thumb !== false ? thumbPosition : undefined}
448
448
  data-thumb-travels={_thumbTravels ? "true" : "false"}
449
449
  data-thumb-reserved={_thumbReserved ? "true" : "false"}
450
- data-fill-rounded={fillRounded ? "true" : undefined}
450
+ data-fill-rounded={fillRounded ? "true" : "false"}
451
451
  data-size={!unstyled ? size : undefined}
452
452
  data-intent={!unstyled ? intent : undefined}
453
453
  data-disabled={disabled ? "true" : undefined}
@@ -80,8 +80,8 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children" |
80
80
  */
81
81
  thumbReserve?: boolean;
82
82
  /**
83
- * Round the fill's leading edge (instead of a flat cut), giving the
84
- * "pill inside a pill" look.
83
+ * Round the fill's leading edge, giving the "pill inside a pill" look
84
+ * (the default). `false` cuts it flat.
85
85
  */
86
86
  fillRounded?: boolean;
87
87
  /**
@@ -115,6 +115,9 @@
115
115
  .stuic-slider .fill {
116
116
  position: absolute;
117
117
  background: var(--_fill);
118
+ /* Rounded leading edge ("pill inside a pill") is the default;
119
+ `fillRounded={false}` cuts it flat below. */
120
+ border-radius: var(--stuic-slider-fill-radius, var(--stuic-slider-radius, 9999px));
118
121
  }
119
122
  /* With a reserved thumb (traveling OR pinned), the fill spans the thumb-center
120
123
  travel plus the thumb itself: it reaches the far edge of a traveling thumb and,
@@ -135,9 +138,9 @@
135
138
  height: var(--_fill-len);
136
139
  }
137
140
 
138
- /* Rounded leading edge ("pill inside a pill") */
139
- .stuic-slider[data-fill-rounded="true"] .fill {
140
- border-radius: var(--stuic-slider-fill-radius, var(--stuic-slider-radius, 9999px));
141
+ /* Flat leading edge (opt out of the default pill) */
142
+ .stuic-slider[data-fill-rounded="false"] .fill {
143
+ border-radius: 0;
141
144
  }
142
145
 
143
146
  /* ============================================================================
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.190.0",
3
+ "version": "3.192.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",