@cueplusplus/ui 0.15.0 → 0.16.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 (112) hide show
  1. package/CHANGELOG.md +258 -0
  2. package/README.md +26 -0
  3. package/dist/chat/message-list.js +2 -1
  4. package/dist/chrome/_tabs-scroll.d.ts +17 -1
  5. package/dist/chrome/_tabs-scroll.js +20 -3
  6. package/dist/chrome/app-bar.js +2 -1
  7. package/dist/chrome/band.js +3 -1
  8. package/dist/chrome/panel.js +2 -1
  9. package/dist/color/color-field.d.ts +2 -1
  10. package/dist/color/color-picker.d.ts +2 -1
  11. package/dist/date/date-field.d.ts +2 -1
  12. package/dist/date/date-picker.d.ts +2 -1
  13. package/dist/date/date-range-picker.d.ts +2 -1
  14. package/dist/date/time-field.d.ts +2 -1
  15. package/dist/forms/_chassis-size.d.ts +22 -0
  16. package/dist/forms/_chassis.d.ts +3 -10
  17. package/dist/forms/_chassis.js +2 -1
  18. package/dist/forms/autocomplete.d.ts +2 -1
  19. package/dist/forms/combobox.d.ts +2 -1
  20. package/dist/forms/env-var-input.d.ts +2 -1
  21. package/dist/forms/index.d.ts +2 -1
  22. package/dist/forms/input-group.d.ts +2 -1
  23. package/dist/forms/input.d.ts +2 -1
  24. package/dist/forms/multi-select.d.ts +2 -1
  25. package/dist/forms/otp-field.d.ts +2 -1
  26. package/dist/forms/password-input.d.ts +2 -1
  27. package/dist/forms/scrub-input.d.ts +2 -1
  28. package/dist/forms/search-input.d.ts +2 -1
  29. package/dist/forms/select.d.ts +2 -1
  30. package/dist/forms/select.js +2 -1
  31. package/dist/forms/tags-input.d.ts +2 -1
  32. package/dist/forms/toggle-group.d.ts +1 -1
  33. package/dist/forms/toggle.d.ts +1 -1
  34. package/dist/index.d.ts +2 -1
  35. package/dist/instruments/log-viewer.js +2 -1
  36. package/dist/layout/card.js +3 -1
  37. package/dist/midi/musical-time-input.d.ts +2 -1
  38. package/dist/overlays/_surface.d.ts +1 -1
  39. package/dist/overlays/_surface.js +3 -1
  40. package/dist/primitives/button.js +2 -1
  41. package/dist/primitives/chip.d.ts +1 -1
  42. package/dist/system/_paint-elevation.js +9 -0
  43. package/dist/system/_paint-glass.js +46 -0
  44. package/dist/system/_paint-glow.js +32 -0
  45. package/dist/system/_paint-ground-pattern.js +30 -0
  46. package/dist/system/_paint-rim.js +41 -0
  47. package/dist/system/_paint-stroke-sunken.js +19 -0
  48. package/dist/system/_paint-stroke-surface-1.js +19 -0
  49. package/dist/system/_paint-stroke-surface-2.js +27 -0
  50. package/dist/system/_paint-texture.js +53 -0
  51. package/dist/system/theme-provider.d.ts +47 -36
  52. package/dist/system/theme-provider.js +13 -6
  53. package/manifest/components/alert-dialog.json +11 -0
  54. package/manifest/components/app-bar.json +4 -0
  55. package/manifest/components/autocomplete.json +11 -0
  56. package/manifest/components/band.json +8 -1
  57. package/manifest/components/button.json +4 -0
  58. package/manifest/components/card.json +6 -0
  59. package/manifest/components/channel-matrix.json +11 -0
  60. package/manifest/components/chart-tooltip-content.json +11 -0
  61. package/manifest/components/color-field.json +4 -0
  62. package/manifest/components/color-picker.json +4 -0
  63. package/manifest/components/combobox.json +11 -0
  64. package/manifest/components/command-palette.json +11 -0
  65. package/manifest/components/composer.json +4 -0
  66. package/manifest/components/context-menu.json +11 -0
  67. package/manifest/components/date-field.json +4 -0
  68. package/manifest/components/date-picker.json +4 -0
  69. package/manifest/components/date-range-picker.json +4 -0
  70. package/manifest/components/density.json +8 -2
  71. package/manifest/components/dialog.json +11 -0
  72. package/manifest/components/dmx-bar.json +11 -0
  73. package/manifest/components/dmx-strip.json +11 -0
  74. package/manifest/components/drawer.json +11 -0
  75. package/manifest/components/dropdown-menu.json +11 -0
  76. package/manifest/components/env-var-input.json +4 -0
  77. package/manifest/components/hover-card.json +11 -0
  78. package/manifest/components/input-group.json +4 -0
  79. package/manifest/components/input.json +4 -0
  80. package/manifest/components/log-viewer.json +7 -0
  81. package/manifest/components/menubar.json +11 -0
  82. package/manifest/components/message-list.json +7 -0
  83. package/manifest/components/multi-select.json +11 -0
  84. package/manifest/components/musical-time-input.json +4 -0
  85. package/manifest/components/navigation-menu.json +11 -0
  86. package/manifest/components/number-field.json +4 -0
  87. package/manifest/components/otp-field.json +4 -0
  88. package/manifest/components/panel-header.json +2 -0
  89. package/manifest/components/panel.json +2 -0
  90. package/manifest/components/password-input.json +4 -0
  91. package/manifest/components/popover.json +11 -0
  92. package/manifest/components/preview.json +8 -2
  93. package/manifest/components/scrollable-tabs-list.json +1 -0
  94. package/manifest/components/scrub-input.json +4 -0
  95. package/manifest/components/search-input.json +4 -0
  96. package/manifest/components/segmented-control.json +1 -0
  97. package/manifest/components/select.json +11 -0
  98. package/manifest/components/sheet.json +11 -0
  99. package/manifest/components/tabs.json +1 -0
  100. package/manifest/components/tags-input.json +11 -0
  101. package/manifest/components/textarea.json +4 -0
  102. package/manifest/components/theme-configurator.json +11 -0
  103. package/manifest/components/theme-provider.json +9 -3
  104. package/manifest/components/time-field.json +4 -0
  105. package/manifest/components/toast.json +11 -0
  106. package/manifest/components/toggle-group.json +1 -19
  107. package/manifest/components/toggle.json +0 -6
  108. package/manifest/components/toolbar.json +1 -0
  109. package/manifest/components/tooltip.json +11 -0
  110. package/manifest/manifest.json +59 -59
  111. package/manifest/tokens.json +21 -2
  112. package/package.json +5 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,263 @@
1
1
  # @cueplusplus/ui
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 42021e4: Every overlay draws its shadow from a composite a look pack can move.
8
+
9
+ Dialogs, menus, popovers, tooltips, toasts, drawers, sheets, the command palette, the
10
+ select and combobox popups and the log viewer's jump button all drew
11
+ `shadow-[0_8px_24px_rgba(0,0,0,0.4)]` — the same literal in four places. They now share
12
+ one four-layer recipe: a hairline, a contact shadow, a bloom and an inset top light.
13
+
14
+ **Nothing looks different.** At the defaults three of the four layers are transparent and
15
+ the bloom is that exact literal. Measured rather than asserted: in Chromium the recipe and
16
+ the literal render byte for byte; in WebKit 42 channels of roughly 264,000 differ by 1/255,
17
+ which is compositing rounding and below what a display can show.
18
+
19
+ What it buys is that `--cue-paint-shadow-scale` moves the whole ladder at once and any
20
+ layer switches off by letting its ink resolve to `transparent` — so a pack can give every
21
+ floating surface a deeper shadow, a tighter one, or a lit top edge, without touching a
22
+ component.
23
+
24
+ Overlays also adopt the gradient stroke deferred from the previous release, and keep their
25
+ own rim colour while doing it: the recipe's stops fall back to `--cue-border-overlay`, not
26
+ `--cue-border`, because some themes match that token to the surface on purpose so the rim
27
+ vanishes.
28
+
29
+ `AppWindowFrame` keeps its literal shadow. Its bloom is twice as dark as every other
30
+ surface's, and giving it the shared recipe would have lightened it.
31
+
32
+ - 6e2fd3b: Every edge fade in the library follows one token.
33
+
34
+ `Tabs`, `Toolbar`, `SegmentedControl`, `ScrollableTabsList` and `AppBar` soften their
35
+ overflowing edges with a mask that reached in a fixed 24px. That distance is
36
+ `--cue-paint-fade-length` now, so a `[data-paint]` pack can make every one of them
37
+ shorter or longer at once.
38
+
39
+ **Nothing looks different.** The token's default is the same 24px, measured pixel for
40
+ pixel in Chromium and WebKit — with a 64px override in the same test, so the comparison
41
+ is one that could have failed.
42
+
43
+ `edgeFadeMask(edges, fadePx)` is unchanged when you pass a reach of your own: an explicit
44
+ number still wins, because a caller that has measured its own fade is not asking for the
45
+ axis's.
46
+
47
+ - 40c9de0: `AppBar`'s frosted glass is a look pack's to change, and a consumer's backdrop utilities still work.
48
+
49
+ The bar drew `bg-surface-1/95 backdrop-blur`. It now spends `--cue-paint-glass-filter` and
50
+ `--cue-paint-glass-ink`, so a `[data-paint]` pack can make the chrome heavier, clearer, or
51
+ tinted — or turn the blur off entirely — without touching the component.
52
+
53
+ **Nothing looks different**, measured pixel for pixel against what the bar drew before, in
54
+ Chromium and WebKit, over a hard-edged background so the blur has something to smear.
55
+
56
+ **And `className` still composes.** This is the part worth knowing if you style `AppBar`
57
+ from outside: passing `backdrop-brightness-50` or `backdrop-saturate-150` through
58
+ `className` adds to the bar's blur rather than replacing it. The obvious implementation —
59
+ a plain `backdrop-filter` declaration — would have silently deleted the blur the moment you
60
+ passed any backdrop utility of your own. That case is now a test.
61
+
62
+ The opaque fallback behind `@supports not (backdrop-filter: …)` is unchanged: a browser
63
+ without backdrop filters still gets a solid bar rather than a translucent one over the page.
64
+
65
+ - e53619a: The first look pack: `halo`, and the glow it paints.
66
+
67
+ `<ThemeProvider paint="halo">` — or `data-paint="halo"` on any element — gives primary
68
+ buttons a soft two-stop glow in the theme's own accent, doubling on hover. It is the first
69
+ value the paint axis has other than `none`, so it is also the first time the axis changes
70
+ what a page looks like rather than only standing ready to.
71
+
72
+ **Nothing changes unless you ask.** Unstamped, both stops have zero blur and a transparent
73
+ ink, so the button renders exactly as it did — measured pixel for pixel in Chromium and
74
+ WebKit, alongside the other half of the same test: that stamping `halo` does change it.
75
+
76
+ A pack names no colour of its own. `halo`'s two inks are a share of `--cue-accent`, so it
77
+ reads correctly under all ten shipped themes rather than carrying one palette's blue into
78
+ the others.
79
+
80
+ Two fixes ride along. `--cue-paint-glow-blur` and `-blur-far` were validated against the
81
+ rim's 0–4px rule, so no usable halo radius was accepted — the first pack to set one was
82
+ refused at 12px. They take 0–64px now. And `Button`'s `primary` tone is the only component
83
+ that spends the glow; every other tone is untouched.
84
+
85
+ - 3d0c3fd: A seventh look pack: `grain`, a fine fractal noise over every surface.
86
+
87
+ `<ThemeProvider paint="grain">`, or `<Density paint="grain">` over a subtree, lays a
88
+ 200×200 fractal-noise tile across the surfaces that carry the texture recipe. It is the
89
+ quietest pack in the catalogue by design — 12% opacity, no blend mode — and it is the
90
+ only one that spends `--cue-paint-texture`, a name the library has carried since the
91
+ texture slice with nothing able to use it.
92
+
93
+ Measured from greptile.com, whose ground is an `feTurbulence fractalNoise` data URI at
94
+ 0.85 baseFrequency over five octaves, composited at roughly 12% effective opacity across
95
+ every pixel of the site. The tile here is that filter at those parameters, generated
96
+ rather than copied: it is 331 bytes of SVG inline in the pack, so nothing is fetched and
97
+ no asset ships.
98
+
99
+ **It is deliberately below the bar.** Every other pack clears the threshold this system
100
+ sets for shipping one; `grain` reaches about 62% of its site and ships anyway, as the
101
+ proof that a pack is worth having for what it refuses as much as for what it paints. Its
102
+ `refuses` note names the four of six screens it cannot claim, the twelve divider forms
103
+ and the band-frame furniture that belong to components rather than to a finish, the
104
+ hexagon control silhouette, and the handwriting type stack.
105
+
106
+ Nothing changes for a page that does not ask for it: `grain` is opt-in like every pack,
107
+ and the provider still stamps no `data-paint` at all.
108
+
109
+ - 4d851a0: `Panel` and `Card` can take an inset ring, and a `Band`'s ground can take a repeating pattern.
110
+
111
+ Two more devices reach the paint axis, which means every one of its thirty-one names is now
112
+ spent by something: `--cue-paint-rim-width` and `-rim-ink` draw a hairline ring inside a
113
+ panel's or a card's radius, and `--cue-paint-ground-pattern` with `-size` lays a repeating
114
+ geometry under a band's content.
115
+
116
+ **Nothing changes unless you ask.** The ring's width is `0px` and its ink is transparent;
117
+ the pattern is `none`. Measured pixel for pixel in Chromium and WebKit, with both devices
118
+ switched on in the same tests so the comparisons are ones that could have failed.
119
+
120
+ The ring is a `box-shadow`, so a `Band` or a `Card` that already draws one cannot take it —
121
+ and **form controls deliberately do not**. `forced-colors: active` discards shadows
122
+ entirely, and a control's ring is a focus indicator: it stays a real outline, which is what
123
+ a forced-colors user actually sees.
124
+
125
+ A pattern and a texture are different layers and compose: a pattern is the host's own
126
+ background image, a texture is noise on a pseudo-element above it.
127
+
128
+ - 5fcc6b4: Form controls and cards draw their border as a gradient stroke, so a look pack can tint it.
129
+
130
+ `Input`, `Textarea`, `Select`, `Combobox`, `Autocomplete`, `SearchInput`, `OTPField`,
131
+ `InputGroup`, the date and colour fields, `MusicalTimeInput` and the rest of the recessed
132
+ control family — and `Card` — now paint their line from two background layers with a
133
+ transparent border between them, rather than from `border border-border`.
134
+
135
+ **Nothing looks different.** At the defaults both gradient stops are `--cue-border` and a
136
+ one-colour gradient paints a flat field, so the line is the same one device-pixel of the
137
+ same colour over the same fill. That is measured rather than asserted: the straight edges
138
+ and the interior are pixel-identical in Chromium and WebKit, and with square corners the
139
+ two are identical everywhere. On a rounded corner they differ by sub-pixel antialiasing —
140
+ invisible at 1×, and bounded by the test rather than claimed away.
141
+
142
+ What it buys is that `--cue-paint-stroke-from`, `-to`, `-angle` and `-width` now reach
143
+ every one of those surfaces, so a `[data-paint]` pack can give the whole family a gradient
144
+ edge, a thicker rule or a different angle without touching a component.
145
+
146
+ A seamed `Card` keeps its old border: its fill is `--cue-border` — the gaps between its
147
+ rows _are_ the line — and the recipe is keyed by fill.
148
+
149
+ `Toggle` and `ToggleGroup` also lose ten custom properties from their published manifests.
150
+ They never painted them: the entries came from a type-only import of the control chassis,
151
+ and the manifest lists what a component spends. `--cue-sunken`, `--cue-focus` and
152
+ `--cue-border` were among them, so this was wrong before the gradient stroke existed.
153
+
154
+ - bcd0738: A `Band`'s ground can carry a grain or pattern overlay.
155
+
156
+ `<Band separation="ground">` now spends `--cue-paint-texture`, `-texture-size`,
157
+ `-texture-opacity` and `-texture-blend`, so a `[data-paint]` pack can lay a noise tile or
158
+ a repeating pattern over the band's ground without touching the component.
159
+
160
+ **Nothing changes unless you ask.** At the defaults `texture` is `none` and the overlay
161
+ paints nothing — measured against the same band with _no_ overlay element at all, which is
162
+ the comparison that matters, since the change adds a `::before` where there was none.
163
+
164
+ The overlay is `pointer-events-none` and covers its host exactly, so it never intercepts a
165
+ click or disturbs the band's own background.
166
+
167
+ `--cue-paint-texture` takes a `data:` URI or an absolute `https://` URL and nothing else —
168
+ the token layer ships no binary, and the build says so by name if you try something else.
169
+
170
+ ### Patch Changes
171
+
172
+ - ac712b3: `ThemeProvider`'s `nonce` now documents what it does not reach, which matters more than what it does.
173
+
174
+ Nothing about the prop's behaviour changes. What changes is that its documentation — the prop's
175
+ own docblock, and so the manifest an agent reads — stops promising coverage it never had. If you
176
+ serve a nonce-based Content-Security-Policy, five things are worth checking:
177
+
178
+ - **Your pre-paint `<script>` needs the nonce by hand.** `prepaintScript()` returns the script's
179
+ text; the element is yours, and Next.js never nonces a plain `<script>`.
180
+ - **The density layer reads the nonce from this prop only.** Not from an outer `CSPProvider`, and
181
+ not from an outer `ThemeProvider` — a nested provider that sets its own `overrides.densities`
182
+ needs its own `nonce`. If you render your own `CSPProvider` and leave this prop unset, Base UI's
183
+ pre-hydration `<script>` takes its nonce in server-rendered HTML and its scrollbar rule takes it
184
+ wherever React creates that rule in the browser; the density layer, if you set
185
+ `overrides.densities`, is not covered.
186
+ - **A server-rendered ScrollArea's Base UI style ships without a nonce under Next.js 16**, whatever
187
+ this prop or your own `CSPProvider` says: React's server renderer (19.2, and the canary Next.js 16
188
+ bundles) writes that element's nonce only from a style-specific render option that Next does not
189
+ pass. Allow it by hash —
190
+ `'sha256-kLmvWqfziFavKtqHqRsb90f006UAK2Dmd0It5Iz2KFA='` for Base UI 1.7.0 — or switch Base UI's
191
+ element off and ship its two rules yourself.
192
+ - **Re-rendering your own `CSPProvider` inside needs the nonce on it.** If you turn on Base UI's
193
+ `disableStyleElements`, render `<CSPProvider nonce={nonce} disableStyleElements>` inside
194
+ `ThemeProvider`'s children — with the nonce, or Base UI's pre-hydration `<script>` below it loses
195
+ it.
196
+ - **A policy whose `style-src` (or, without one, `default-src`) carries a nonce or a hash, or lacks
197
+ `'unsafe-inline'`, needs an explicit `style-src-attr 'unsafe-inline'`.** A nonce cannot cover a
198
+ style attribute, the provider root and many components carry one, and a nonce or hash in the
199
+ governing list makes the browser ignore `'unsafe-inline'`.
200
+
201
+ This also corrects three earlier notes. 0.14.1 said Base UI's element appears wherever a Select, a
202
+ Popover or a ScrollArea does: a Popover never renders it, and a Select renders it only once its popup
203
+ mounts in the browser. 0.14.0 said nothing else in this library emits a `<style>` element:
204
+ `ThemeConfigurator` does, with no nonce. And 0.14.0 said `style-src-attr` is `'unsafe-inline'` or
205
+ `'none'` and that only `'none'` drops the root's declarations: a nonce in `style-src` drops them too
206
+ when no `style-src-attr` is set, and `'unsafe-hashes'` with a hash of each value is a third form. `docs/CONSUMING.md` §4.5 has the whole of it, including
207
+ `paint="grain"` needing `img-src data:`, statically rendered pages, and rolling out under
208
+ `Content-Security-Policy-Report-Only` first.
209
+
210
+ - 3fc83a6: Four more look packs: `hatch`, `lift`, `frost` and `bevel`.
211
+
212
+ `<ThemeProvider paint="frost">` — or `data-paint` on any element — now picks from six
213
+ finishes rather than two:
214
+
215
+ - **`hatch`** lays a faint diagonal weave under a band's ground and puts a hairline ring on
216
+ the panels and cards above it.
217
+ - **`lift`** deepens the shadow ladder and adds a lit top edge, so overlays read as sitting
218
+ above the page rather than in it.
219
+ - **`frost`** is heavy glass on the chrome, a long edge fade, and a shadow that reads as
220
+ distance.
221
+ - **`bevel`** gives surfaces a gradient edge that catches light at the top and loses it at
222
+ the bottom, with a lighter glass over the chrome.
223
+
224
+ **No pack names a colour of its own.** Every ink is a share of the theme's own palette, so
225
+ each of the six reads correctly under all ten shipped themes rather than carrying one
226
+ site's blue into the others — and the build refuses a literal hue by name if one is ever
227
+ written.
228
+
229
+ Each pack is a complete statement of a finish rather than a diff against whatever is above
230
+ it, so switching between them — or nesting one inside another — changes only what the pack
231
+ is for. `paint="none"` inside a packed subtree really does reset it, and still renders
232
+ identically to no pack at all.
233
+
234
+ `@cueplusplus/ui`'s manifest records the four new pack values against the three components that stamp the axis, which is how the studio and the docs playground know to offer them.
235
+
236
+ - f12d280: The READMEs now describe the package you install, not the one from three releases ago.
237
+
238
+ `@cueplusplus/tokens`'s README opened by calling itself "two of the system's three DOM axes" and
239
+ called `axes.css` "the two axes". It is four of five — density, font, style set and paint live
240
+ here, and the fifth, `[data-theme]`, is the theme packages' — so its axis table gains `[data-style]`
241
+ (page composition, 13 names) and `[data-paint]` (the finish, 18 names and 13 inks, off until a page
242
+ stamps it), its export table names `STYLE_SETS`, `PAINTS`, the style and paint contracts and the
243
+ paint inks beside the rest, and its quick start says the rule that applies to `DENSITIES` applies
244
+ to all four: build a picker from the exported list, never a hand-written one.
245
+
246
+ `@cueplusplus/ui`'s quick start now says what the provider's four axes each change, that `paint` is
247
+ off until you pass a pack, and where the seven packs and a nonce-based Content-Security-Policy are
248
+ written up — including that `paint="grain"` needs `img-src data:`. It also says which axes a
249
+ subtree can change: `<Density>` re-scopes `density`, `styleSet` and `paint`, and the font pairing is
250
+ the root's alone, because it lives on `<html>`.
251
+
252
+ - Updated dependencies [e53619a]
253
+ - Updated dependencies [840aa92]
254
+ - Updated dependencies [4eb3b42]
255
+ - Updated dependencies [3fc83a6]
256
+ - Updated dependencies [3d0c3fd]
257
+ - Updated dependencies [0371094]
258
+ - Updated dependencies [f12d280]
259
+ - @cueplusplus/tokens@0.16.0
260
+
3
261
  ## 0.15.0
4
262
 
5
263
  ### Minor Changes
package/README.md CHANGED
@@ -53,6 +53,32 @@ To stop the defaults flashing on first paint, add `prepaintScript()` beside the
53
53
  both the same array — [§4 of `CONSUMING.md`](https://github.com/cueplusplus/cue-ui/blob/main/docs/CONSUMING.md#4-the-provider)
54
54
  is the whole layout.
55
55
 
56
+ Beside `theme` and `mode`, the provider takes four axes, and each one changes a single thing:
57
+ `density` the geometry, `font` the typeface pairing, `styleSet` the page composition, and `paint`
58
+ the finish — rims, strokes, elevation, glow, glass, texture and edge fades. `paint` is off unless
59
+ you ask for it: the provider stamps no `data-paint`, so a page looks exactly as it did until you
60
+ pass one of `none`, `halo`, `hatch`, `lift`, `frost`, `bevel` or `grain`. Any subtree can take its
61
+ own with `<Density paint="frost">` — the same island that re-scopes `density` and `styleSet`. The
62
+ font pairing is the one axis a subtree cannot change: it lives on `<html>`, so it is the root's.
63
+
64
+ ```tsx
65
+ import cue from "@cueplusplus/theme-cue";
66
+ import { ThemeProvider } from "@cueplusplus/ui/system";
67
+
68
+ export function App({ children }: { children: React.ReactNode }) {
69
+ return (
70
+ <ThemeProvider themes={[cue]} theme="cue" density="compact" mode="system" paint="halo">
71
+ {children}
72
+ </ThemeProvider>
73
+ );
74
+ }
75
+ ```
76
+
77
+ The seven packs, where each was measured from and what each refuses:
78
+ <https://ui.cueplusplus.com/docs/paint>. Serving a nonce-based Content-Security-Policy? Read
79
+ [§4.5 of `CONSUMING.md`](https://github.com/cueplusplus/cue-ui/blob/main/docs/CONSUMING.md#45-content-security-policy-the-nonce-prop-and-what-it-does-not-reach)
80
+ before you ship it — `paint="grain"` alone needs `img-src data:`.
81
+
56
82
  ## What it ships
57
83
 
58
84
  Common components come from the root entry. Groups that need an optional peer have a subpath of
@@ -2,6 +2,7 @@
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { ChevronDownGlyph } from "./_glyphs.js";
4
4
  import { useIsomorphicLayoutEffect } from "../system/use-isomorphic-layout-effect.js";
5
+ import { ELEVATION_2 } from "../system/_paint-elevation.js";
5
6
  import * as React from "react";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
7
8
  //#region src/chat/message-list.tsx
@@ -131,7 +132,7 @@ const MessageList = React.forwardRef(function MessageList({ className, children,
131
132
  }), behind ? /* @__PURE__ */ jsxs("button", {
132
133
  type: "button",
133
134
  "data-slot": "message-list-jump",
134
- className: "absolute bottom-(--cue-space-4) left-1/2 flex -translate-x-1/2 cursor-pointer items-center gap-(--cue-space-2) rounded-(--radius-control) border border-border-overlay bg-surface-2 px-(--cue-space-4) py-(--cue-space-2) font-mono text-(length:--cue-text-label) text-fg shadow-[0_8px_24px_rgba(0,0,0,0.4)] outline-none hover:border-accent hover:text-accent focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-1 focus-visible:outline-accent",
135
+ className: `absolute bottom-(--cue-space-4) left-1/2 flex -translate-x-1/2 cursor-pointer items-center gap-(--cue-space-2) rounded-(--radius-control) border border-border-overlay bg-surface-2 px-(--cue-space-4) py-(--cue-space-2) font-mono text-(length:--cue-text-label) text-fg ${ELEVATION_2} outline-none hover:border-accent hover:text-accent focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-1 focus-visible:outline-accent`,
135
136
  onClick: jumpToLatest,
136
137
  children: [/* @__PURE__ */ jsx(ChevronDownGlyph, { className: "size-icon-sm" }), jumpLabel]
137
138
  }) : null]
@@ -56,8 +56,24 @@ declare function computeScrollEdges(scrollLeft: number, scrollWidth: number, cli
56
56
  * overflowing edge is often the gap between two tabs, and fading empty space is
57
57
  * invisible — hence the chevron buttons in the component.
58
58
  *
59
+ * **The default reach is a custom property, not a number**, so a look pack can
60
+ * lengthen or shorten every edge fade in the library at once. It is written as
61
+ * `var(--cue-paint-fade-length,24px)` with the fallback spelled out, because an
62
+ * ink or a paint name is declared with a `:root` default here but the mask is
63
+ * also built by this function in isolation — a test calling it with no DOM
64
+ * would otherwise produce a gradient with an unresolvable stop.
65
+ *
66
+ * `--cue-paint-fade-length` is `24px` at `:root` and {@link TAB_FADE_PX} is 24,
67
+ * so the swap renders identically: measured pixel for pixel in Chromium and
68
+ * WebKit, with a 64px pack changing 15,360 channels to prove the probe could
69
+ * see a difference.
70
+ *
71
+ * An explicit `fadePx` still wins, and stays a plain number: a caller that has
72
+ * measured its own reach is not asking for the axis's.
73
+ *
59
74
  * @param edges Which edges overflow, from {@link computeScrollEdges}.
60
- * @param fadePx How far the fade reaches in. Defaults to {@link TAB_FADE_PX}.
75
+ * @param fadePx How far the fade reaches in. Omitted, it follows
76
+ * `--cue-paint-fade-length`, whose own default is {@link TAB_FADE_PX}.
61
77
  * @returns A `linear-gradient(...)` value, or `undefined` when nothing
62
78
  * overflows — no mask property at all, rather than a fully opaque one, so the
63
79
  * common case creates no compositing layer.
@@ -59,15 +59,32 @@ function computeScrollEdges(scrollLeft, scrollWidth, clientWidth) {
59
59
  * overflowing edge is often the gap between two tabs, and fading empty space is
60
60
  * invisible — hence the chevron buttons in the component.
61
61
  *
62
+ * **The default reach is a custom property, not a number**, so a look pack can
63
+ * lengthen or shorten every edge fade in the library at once. It is written as
64
+ * `var(--cue-paint-fade-length,24px)` with the fallback spelled out, because an
65
+ * ink or a paint name is declared with a `:root` default here but the mask is
66
+ * also built by this function in isolation — a test calling it with no DOM
67
+ * would otherwise produce a gradient with an unresolvable stop.
68
+ *
69
+ * `--cue-paint-fade-length` is `24px` at `:root` and {@link TAB_FADE_PX} is 24,
70
+ * so the swap renders identically: measured pixel for pixel in Chromium and
71
+ * WebKit, with a 64px pack changing 15,360 channels to prove the probe could
72
+ * see a difference.
73
+ *
74
+ * An explicit `fadePx` still wins, and stays a plain number: a caller that has
75
+ * measured its own reach is not asking for the axis's.
76
+ *
62
77
  * @param edges Which edges overflow, from {@link computeScrollEdges}.
63
- * @param fadePx How far the fade reaches in. Defaults to {@link TAB_FADE_PX}.
78
+ * @param fadePx How far the fade reaches in. Omitted, it follows
79
+ * `--cue-paint-fade-length`, whose own default is {@link TAB_FADE_PX}.
64
80
  * @returns A `linear-gradient(...)` value, or `undefined` when nothing
65
81
  * overflows — no mask property at all, rather than a fully opaque one, so the
66
82
  * common case creates no compositing layer.
67
83
  */
68
- function edgeFadeMask(edges, fadePx = 24) {
84
+ function edgeFadeMask(edges, fadePx) {
69
85
  if (!edges.start && !edges.end) return void 0;
70
- return `linear-gradient(to right, ${edges.start ? `transparent 0px, black ${fadePx}px` : "black 0px"}, ${edges.end ? `black calc(100% - ${fadePx}px), transparent 100%` : "black 100%"})`;
86
+ const reach = fadePx === void 0 ? `var(--cue-paint-fade-length,24px)` : `${fadePx}px`;
87
+ return `linear-gradient(to right, ${edges.start ? `transparent 0px, black ${reach}` : "black 0px"}, ${edges.end ? `black calc(100% - ${reach}), transparent 100%` : "black 100%"})`;
71
88
  }
72
89
  //#endregion
73
90
  export { TAB_FADE_PX, computeScrollEdges, edgeFadeMask };
@@ -2,6 +2,7 @@ import { cn } from "../lib/cn.js";
2
2
  import { Link } from "../layout/link.js";
3
3
  import { Separator } from "../primitives/separator.js";
4
4
  import { EdgeScrollNav } from "./_edge-scroller.js";
5
+ import { GLASS_CLASSES } from "../system/_paint-glass.js";
5
6
  import * as React from "react";
6
7
  import { jsx } from "react/jsx-runtime";
7
8
  //#region src/chrome/app-bar.tsx
@@ -53,7 +54,7 @@ const AppBarRoot = React.forwardRef(function AppBarRoot({ className, sticky = tr
53
54
  return /* @__PURE__ */ jsx("header", {
54
55
  ref,
55
56
  "data-slot": "app-bar",
56
- className: cn("flex h-(--cue-chrome-toolbar) w-full shrink-0 items-center gap-(--cue-space-4) border-b border-border bg-surface-1/95 px-(--cue-space-5) backdrop-blur", sticky ? "sticky top-0 z-40" : null, className),
57
+ className: cn(`flex h-(--cue-chrome-toolbar) w-full shrink-0 items-center gap-(--cue-space-4) border-b border-border px-(--cue-space-5) ${GLASS_CLASSES}`, sticky ? "sticky top-0 z-40" : null, className),
57
58
  ...elementProps
58
59
  });
59
60
  });
@@ -1,6 +1,8 @@
1
1
  import { cn } from "../lib/cn.js";
2
2
  import { Container } from "../layout/container.js";
3
3
  import { bandClasses } from "./_band.js";
4
+ import { TEXTURE_CLASSES } from "../system/_paint-texture.js";
5
+ import { GROUND_PATTERN_CLASSES } from "../system/_paint-ground-pattern.js";
4
6
  import * as React from "react";
5
7
  import { jsx } from "react/jsx-runtime";
6
8
  //#region src/chrome/band.tsx
@@ -28,7 +30,7 @@ import { jsx } from "react/jsx-runtime";
28
30
  */
29
31
  const SEPARATION_PAINT = {
30
32
  rule: "border-(--cue-band-rule-color)",
31
- ground: "bg-(--cue-band-ground)",
33
+ ground: `bg-(--cue-band-ground) ${GROUND_PATTERN_CLASSES} ${TEXTURE_CLASSES}`,
32
34
  bleed: null,
33
35
  none: null
34
36
  };
@@ -1,4 +1,5 @@
1
1
  import { cn } from "../lib/cn.js";
2
+ import { RIM_INSET } from "../system/_paint-rim.js";
2
3
  import * as React from "react";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  //#region src/chrome/panel.tsx
@@ -28,7 +29,7 @@ const Panel = React.forwardRef(function Panel({ className, ...elementProps }, re
28
29
  return /* @__PURE__ */ jsx("div", {
29
30
  ref,
30
31
  "data-slot": "panel",
31
- className: cn("overflow-hidden rounded-(--radius-surface) border border-border bg-surface-1", className),
32
+ className: cn(`overflow-hidden rounded-(--radius-surface) border border-border bg-surface-1 ${RIM_INSET}`, className),
32
33
  ...elementProps
33
34
  });
34
35
  });
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "../forms/_chassis.js";
1
+ import { FormControlSize } from "../forms/_chassis-size.js";
2
+ import "../forms/_chassis.js";
2
3
  import * as React from "react";
3
4
  import { ColorFieldProps, InputProps } from "react-aria-components";
4
5
  //#region src/color/color-field.d.ts
@@ -1,5 +1,6 @@
1
1
  import { ColorFormat } from "./_convert.js";
2
- import { FormControlSize } from "../forms/_chassis.js";
2
+ import { FormControlSize } from "../forms/_chassis-size.js";
3
+ import "../forms/_chassis.js";
3
4
  import * as React from "react";
4
5
  //#region src/color/color-picker.d.ts
5
6
  interface ColorPickerProps {
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "../forms/_chassis.js";
1
+ import { FormControlSize } from "../forms/_chassis-size.js";
2
+ import "../forms/_chassis.js";
2
3
  import * as React from "react";
3
4
  import { DateFieldProps, DateInputProps, DateValue } from "react-aria-components";
4
5
  //#region src/date/date-field.d.ts
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "../forms/_chassis.js";
1
+ import { FormControlSize } from "../forms/_chassis-size.js";
2
+ import "../forms/_chassis.js";
2
3
  import { CalendarProps } from "./calendar.js";
3
4
  import * as React from "react";
4
5
  //#region src/date/date-picker.d.ts
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "../forms/_chassis.js";
1
+ import { FormControlSize } from "../forms/_chassis-size.js";
2
+ import "../forms/_chassis.js";
2
3
  import { CalendarProps } from "./calendar.js";
3
4
  import * as React from "react";
4
5
  import { DateRange } from "react-day-picker";
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "../forms/_chassis.js";
1
+ import { FormControlSize } from "../forms/_chassis-size.js";
2
+ import "../forms/_chassis.js";
2
3
  import * as React from "react";
3
4
  import { DateInputProps, TimeFieldProps, TimeValue } from "react-aria-components";
4
5
  //#region src/date/time-field.d.ts
@@ -0,0 +1,22 @@
1
+ //#region src/forms/_chassis-size.d.ts
2
+ /**
3
+ * The rung of the density control ladder a form control sits on.
4
+ *
5
+ * Named `FormControlSize` rather than `ControlSize` because
6
+ * `@cueplusplus/ui/system` already exports a `ControlSize` for
7
+ * `useControlHeight`, and the root barrel re-exports both.
8
+ *
9
+ * **Its own module, and the reason is the manifest.** `@repo/docgen`
10
+ * attributes a custom property to a component by the FILE its class string
11
+ * lives in, and it does not distinguish a type-only import from a value one.
12
+ * While this type lived in `_chassis.ts`, `Toggle` and `ToggleGroup` — which
13
+ * import nothing from there but the type and paint their own `toggleVariants`
14
+ * box — advertised `--cue-paint-stroke-*` in their published manifests. Found
15
+ * in review of the stroke slice; the manifest is a public contract, so a
16
+ * component claiming a token it never paints is wrong in the way that matters.
17
+ *
18
+ * Nothing but a type may join this file, for the same reason.
19
+ */
20
+ type FormControlSize = "sm" | "md" | "lg";
21
+ //#endregion
22
+ export { FormControlSize };
@@ -1,13 +1,6 @@
1
1
  import { ClassProp, VariantProps } from "../lib/cva.js";
2
+ import { FormControlSize } from "./_chassis-size.js";
2
3
  //#region src/forms/_chassis.d.ts
3
- /**
4
- * The rung of the density control ladder a form control sits on.
5
- *
6
- * Named `FormControlSize` rather than `ControlSize` because
7
- * `@cueplusplus/ui/system` already exports a `ControlSize` for
8
- * `useControlHeight`, and the root barrel re-exports both.
9
- */
10
- type FormControlSize = "sm" | "md" | "lg";
11
4
  /**
12
5
  * Everything the recessed control chassis paints except the box itself.
13
6
  *
@@ -97,7 +90,7 @@ type FormControlSize = "sm" | "md" | "lg";
97
90
  * `focus-visible:outline-solid` re-declares the outline style the sibling
98
91
  * `outline-none` switched off — see the note on `buttonChassis`.
99
92
  */
100
- declare const controlSurface = "w-full rounded-(--radius-control) border border-border bg-sunken font-mono text-fg placeholder:text-fg-subtle outline-none focus:border-border-strong focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-(--cue-focus) disabled:opacity-40 data-[disabled]:opacity-40 data-[invalid]:border-danger data-[invalid]:focus-visible:outline-danger";
93
+ declare const controlSurface = "w-full rounded-(--radius-control) border-transparent border-(length:--cue-paint-stroke-width) bg-[linear-gradient(var(--cue-sunken),var(--cue-sunken)),linear-gradient(var(--cue-paint-stroke-angle),var(--cue-paint-stroke-from,var(--cue-border)),var(--cue-paint-stroke-to,var(--cue-border)))] bg-origin-border [background-clip:padding-box,border-box] font-mono text-fg placeholder:text-fg-subtle outline-none focus:border-border-strong focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-(--cue-focus) disabled:opacity-40 data-[disabled]:opacity-40 data-[invalid]:border-danger data-[invalid]:focus-visible:outline-danger";
101
94
  /**
102
95
  * The full recessed-control recipe, one variant per rung of the control ladder.
103
96
  *
@@ -161,4 +154,4 @@ declare const bareControlClasses = "min-w-0 flex-1 border-0 bg-transparent p-0 f
161
154
  */
162
155
  declare const controlGroupFocusClasses = "focus-within:border-border-strong has-focus-visible:outline-2 has-focus-visible:outline-solid has-focus-visible:-outline-offset-1 has-focus-visible:outline-(--cue-focus) data-[invalid]:has-focus-visible:outline-danger";
163
156
  //#endregion
164
- export { ControlVariantProps, FormControlSize, bareControlClasses, controlClasses, controlGroupFocusClasses, controlSurface, controlVariants };
157
+ export { ControlVariantProps, type FormControlSize, bareControlClasses, controlClasses, controlGroupFocusClasses, controlSurface, controlVariants };
@@ -1,4 +1,5 @@
1
1
  import { cva } from "../lib/cva.js";
2
+ import { STROKE_SUNKEN } from "../system/_paint-stroke-sunken.js";
2
3
  //#region src/forms/_chassis.ts
3
4
  /**
4
5
  * Everything the recessed control chassis paints except the box itself.
@@ -89,7 +90,7 @@ import { cva } from "../lib/cva.js";
89
90
  * `focus-visible:outline-solid` re-declares the outline style the sibling
90
91
  * `outline-none` switched off — see the note on `buttonChassis`.
91
92
  */
92
- const controlSurface = "w-full rounded-(--radius-control) border border-border bg-sunken font-mono text-fg placeholder:text-fg-subtle outline-none focus:border-border-strong focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-(--cue-focus) disabled:opacity-40 data-[disabled]:opacity-40 data-[invalid]:border-danger data-[invalid]:focus-visible:outline-danger";
93
+ const controlSurface = `w-full rounded-(--radius-control) ${STROKE_SUNKEN} font-mono text-fg placeholder:text-fg-subtle outline-none focus:border-border-strong focus-visible:outline-2 focus-visible:outline-solid focus-visible:-outline-offset-1 focus-visible:outline-(--cue-focus) disabled:opacity-40 data-[disabled]:opacity-40 data-[invalid]:border-danger data-[invalid]:focus-visible:outline-danger`;
93
94
  /**
94
95
  * The full recessed-control recipe, one variant per rung of the control ladder.
95
96
  *
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "./_chassis.js";
1
+ import { FormControlSize } from "./_chassis-size.js";
2
+ import "./_chassis.js";
2
3
  import * as React from "react";
3
4
  //#region src/forms/autocomplete.d.ts
4
5
  interface AutocompleteProps {
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "./_chassis.js";
1
+ import { FormControlSize } from "./_chassis-size.js";
2
+ import "./_chassis.js";
2
3
  import * as React from "react";
3
4
  //#region src/forms/combobox.d.ts
4
5
  /** One row in a {@link Combobox} or {@link MultiSelect}. */
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "./_chassis.js";
1
+ import { FormControlSize } from "./_chassis-size.js";
2
+ import "./_chassis.js";
2
3
  import * as React from "react";
3
4
  //#region src/forms/env-var-input.d.ts
4
5
  /**
@@ -1,4 +1,5 @@
1
- import { ControlVariantProps, FormControlSize, bareControlClasses, controlClasses, controlGroupFocusClasses, controlSurface, controlVariants } from "./_chassis.js";
1
+ import { FormControlSize } from "./_chassis-size.js";
2
+ import { ControlVariantProps, bareControlClasses, controlClasses, controlGroupFocusClasses, controlSurface, controlVariants } from "./_chassis.js";
2
3
  import { Autocomplete, AutocompleteProps } from "./autocomplete.js";
3
4
  import { ButtonGroup, ButtonGroupOrientation, ButtonGroupProps } from "./button-group.js";
4
5
  import { Checkbox, CheckboxGroup, CheckboxGroupProps, CheckboxProps } from "./checkbox.js";
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "./_chassis.js";
1
+ import { FormControlSize } from "./_chassis-size.js";
2
+ import "./_chassis.js";
2
3
  import * as React from "react";
3
4
  //#region src/forms/input-group.d.ts
4
5
  interface InputGroupProps extends React.ComponentPropsWithoutRef<"div"> {
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "./_chassis.js";
1
+ import { FormControlSize } from "./_chassis-size.js";
2
+ import "./_chassis.js";
2
3
  import * as React from "react";
3
4
  import { Input } from "@base-ui/react/input";
4
5
  //#region src/forms/input.d.ts
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "./_chassis.js";
1
+ import { FormControlSize } from "./_chassis-size.js";
2
+ import "./_chassis.js";
2
3
  import { ComboboxOption } from "./combobox.js";
3
4
  import * as React from "react";
4
5
  //#region src/forms/multi-select.d.ts
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "./_chassis.js";
1
+ import { FormControlSize } from "./_chassis-size.js";
2
+ import "./_chassis.js";
2
3
  import * as React from "react";
3
4
  import { OTPField } from "@base-ui/react/otp-field";
4
5
  //#region src/forms/otp-field.d.ts
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "./_chassis.js";
1
+ import { FormControlSize } from "./_chassis-size.js";
2
+ import "./_chassis.js";
2
3
  import { InputProps } from "./input.js";
3
4
  import * as React from "react";
4
5
  //#region src/forms/password-input.d.ts
@@ -1,4 +1,5 @@
1
- import { FormControlSize } from "./_chassis.js";
1
+ import { FormControlSize } from "./_chassis-size.js";
2
+ import "./_chassis.js";
2
3
  import * as React from "react";
3
4
  //#region src/forms/scrub-input.d.ts
4
5
  interface ScrubInputProps extends Omit<React.ComponentPropsWithoutRef<"div">, "onChange" | "defaultValue" | "onDragStart" | "onDragEnd"> {