@pie-players/pie-theme 0.3.64 → 0.3.66

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/README.md CHANGED
@@ -2,14 +2,21 @@
2
2
 
3
3
  Shared PIE theming primitives and the `pie-theme` custom element.
4
4
 
5
- `pie-theme` resolves canonical PIE variables (`--pie-*`) with this precedence:
5
+ `pie-theme` resolves PIE theme tokens (`--pie-*`) with this precedence:
6
6
 
7
7
  1. Base PIE theme (`theme=light|dark|auto`)
8
8
  2. Provider adapter output (for example DaisyUI tokens)
9
- 3. Selected color scheme (`scheme`)
9
+ 3. Resolved registered color scheme (`scheme`)
10
10
  4. Explicit `variables` overrides
11
11
 
12
- ## Usage
12
+ Base themes and built-in color schemes are authored once in TypeScript. The
13
+ runtime resolver and the checked-in CSS adapters use those same definitions, so
14
+ the managed custom-element path and the stylesheet-only path cannot carry
15
+ different palettes.
16
+
17
+ ## Entrypoints and styles
18
+
19
+ Importing the package root registers `<pie-theme>`:
13
20
 
14
21
  ```ts
15
22
  import "@pie-players/pie-theme";
@@ -18,19 +25,59 @@ import "@pie-players/pie-theme/color-schemes.css";
18
25
  import "@pie-players/pie-theme/font-sizes.css";
19
26
  ```
20
27
 
28
+ The lower-level element entrypoint is intentionally side-effect-free:
29
+
30
+ ```ts
31
+ import { definePieTheme } from "@pie-players/pie-theme/theme-element";
32
+
33
+ definePieTheme();
34
+ ```
35
+
36
+ Keep that distinction when integrating or bundling the package. Existing hosts
37
+ depend on the root entrypoint registering the element and on `theme-element`
38
+ waiting for an explicit call.
39
+
40
+ The four stylesheet artifacts remain available at both their package export and
41
+ literal `dist` paths:
42
+
43
+ - `tokens.css`
44
+ - `color-schemes.css`
45
+ - `font-sizes.css`
46
+ - `components.css`
47
+
48
+ They are unlayered deliberately. In stylesheet-only delivery, a host may
49
+ continue to override public tokens through normal source order, specificity, or
50
+ `!important`.
51
+ The element-name portion of the base-theme adapter uses `:where(...)`, keeping
52
+ its specificity below a later `[data-color-scheme]` rule. In a stylesheet-only
53
+ integration, that lets a host scheme override the generated base theme through
54
+ the normal cascade without `!important`.
55
+
21
56
  ```html
22
57
  <pie-theme theme="auto" scope="document">
23
58
  <pie-section-player></pie-section-player>
24
59
  </pie-theme>
25
60
  ```
26
61
 
27
- ## Custom element API
62
+ ## Custom element interface
28
63
 
29
64
  - `theme`: `light | dark | auto`
30
65
  - `scope`: `self | document`
31
- - `provider`: provider id or `auto` (default)
32
- - `scheme`: color scheme id (`default` by default)
33
- - `variables`: JSON object of CSS variable overrides
66
+ - `provider`: provider id, `auto` (default), or `none`
67
+ - `scheme`: requested color-scheme id (`default` by default)
68
+ - `variables`: JSON object of CSS custom-property overrides
69
+
70
+ `default` means no named color scheme; the base theme and provider still apply.
71
+ For any other value, requested and resolved scheme are separate states. If the
72
+ id is unavailable, `<pie-theme>` keeps it in both `scheme` and
73
+ `data-color-scheme`, renders the safe base/provider result plus any explicit
74
+ `variables`, and automatically uses it if a matching custom scheme is registered
75
+ later. Retaining `data-color-scheme` preserves a selector hook, but does not give
76
+ an unregistered scheme managed precedence. A mounted `<pie-theme>` writes its
77
+ resolved tokens inline, so a competing host selector must use `!important`; use
78
+ a Registered Custom Scheme when the scheme needs normal managed precedence. A
79
+ stylesheet-only integration without a mounted `<pie-theme>` continues to use
80
+ the normal cascade.
34
81
 
35
82
  ## Provider Adapter API
36
83
 
@@ -51,14 +98,50 @@ const myProvider: ThemeProviderAdapter = {
51
98
  registerPieThemeProvider(myProvider);
52
99
  ```
53
100
 
54
- ## Custom Color Schemes
101
+ ## Runtime theme interface
102
+
103
+ The package exposes four operations:
104
+
105
+ ```ts
106
+ resolvePieTheme(input): ThemeResolution
107
+ listPieColorSchemes(): ColorSchemeSnapshot
108
+ observePieColorSchemes(listener): Unsubscribe
109
+ registerPieColorSchemes(entries): RegistrationReceipt
110
+ ```
111
+
112
+ `resolvePieTheme()` accepts `baseTheme` (`light` or `dark`),
113
+ `requestedScheme`, `providerVariables`, and final `variables`. Its result
114
+ contains `baseTheme`, `requestedScheme`, `resolvedScheme`, `status` (`default`,
115
+ `built-in`, `custom`, or `unavailable`), final `variables`, and `diagnostics`.
116
+
117
+ `resolvePieTheme()` returns the requested id, resolution status, resolved
118
+ descriptor when available, final immutable token values, and diagnostics.
119
+ `listPieColorSchemes()` returns an immutable snapshot whose `schemes` list keeps
120
+ the built-in order and starts with the `default` descriptor. Catalog previews
121
+ resolve each scheme over PIE's canonical light base, then project
122
+ `--pie-background`, `--pie-text`, and `--pie-primary`; they are not separately
123
+ authored swatches or a promise to mirror a host-specific provider. Projection
124
+ preserves a deterministic opaque subset: named colors, three- or six-digit hex,
125
+ non-alpha `rgb()`, `hsl()`, `hwb()`, `lab()`, `lch()`, `oklab()`, `oklch()`,
126
+ and standard CSS Color 4 `color()` spaces. Transparent, explicit-alpha,
127
+ relative, nested, context-dependent, malformed, or unsupported forms fall back
128
+ to the canonical opaque preview swatch, so a preview never inherits accidental
129
+ colors from the picker itself. The authored theme variable is not changed by
130
+ that fallback.
131
+
132
+ `observePieColorSchemes()` calls the listener immediately with the current
133
+ snapshot, then once after each successful catalog-changing registration or
134
+ unregistration. Listener failures are isolated, and reentrant mutations produce
135
+ later coherent snapshots rather than interrupting the current notification.
136
+
137
+ ## Registered custom schemes
55
138
 
56
139
  Register consumer-defined schemes without modifying framework source:
57
140
 
58
141
  ```ts
59
142
  import { registerPieColorSchemes } from "@pie-players/pie-theme";
60
143
 
61
- registerPieColorSchemes([
144
+ const registration = registerPieColorSchemes([
62
145
  {
63
146
  id: "district-high-contrast",
64
147
  name: "District High Contrast",
@@ -68,21 +151,84 @@ registerPieColorSchemes([
68
151
  "--pie-text": "#ffffff",
69
152
  "--pie-primary": "#00ffff",
70
153
  },
71
- preview: {
72
- bg: "#000000",
73
- text: "#ffffff",
74
- primary: "#00ffff",
75
- },
76
154
  },
77
155
  ]);
156
+
157
+ // Removes only this registration. Safe to call more than once.
158
+ registration.unregister();
78
159
  ```
79
160
 
80
161
  Then activate with `scheme="district-high-contrast"` on `pie-theme`.
81
162
 
163
+ Registered custom schemes are partial overlays. Each entry validates atomically
164
+ against the token registry's scheme-participation metadata; an invalid entry is
165
+ rejected without dropping valid sibling entries. Built-in ids, `default`,
166
+ unknown tokens, and excluded/private/legacy tokens cannot be registered. The
167
+ latest valid registration for a custom id wins, and an older receipt cannot
168
+ remove that newer definition. Validation returns structured diagnostics and
169
+ warns concisely by default; it does not throw for ordinary invalid input.
170
+ Contrast diagnostics inspect affected semantic relationships in the fully
171
+ resolved custom palette and remain non-blocking because that palette is
172
+ host-owned.
173
+
174
+ Use the final `variables` property for deliberate per-instance overrides. Use a
175
+ CSS selector keyed by `data-color-scheme` only when a CSS-only scheme and its
176
+ cascade requirements are intentional. Such a selector follows the normal
177
+ cascade in a stylesheet-only integration; when it competes with a mounted
178
+ `<pie-theme>`'s inline tokens, it needs `!important`.
179
+
180
+ ## Fixed hues
181
+
182
+ Some components paint a hue the palette does not own: a data encoding, like the
183
+ periodic table's category fills. `--pie-fixed-hue-collapse` is the share by
184
+ which such a hue folds into the palette — the component mixes its own value
185
+ towards `--pie-background-dark` and `--pie-text` by this share, so `0%` renders
186
+ the authored hue exactly and `100%` removes it.
187
+
188
+ Base Themes set `0%`, because a full palette leaves a hue encoding readable.
189
+ Every colour scheme sets `100%`: a two-colour palette is a promise, and a hue
190
+ that survives it is a colour the learner did not choose. A registered custom
191
+ scheme collapses without declaring anything, and keeps its encodings by setting
192
+ `--pie-fixed-hue-collapse: 0%` itself.
193
+
194
+ Both ends of the mix are exact, so this costs nothing under a Base Theme. What
195
+ it costs under a scheme is the encoding: a component collapsing hues has to
196
+ carry category, state or series somewhere else — a label, a filter, or the
197
+ accessible name.
198
+
82
199
  ## DaisyUI Integration
83
200
 
84
201
  - If DaisyUI tokens are present on the target scope, `pie-theme` uses the built-in `daisyui` provider adapter.
85
202
  - Override precedence is: base PIE -> provider output -> scheme -> `variables`.
203
+ - `provider="none"` (`PIE_THEME_PROVIDER_NONE`) resolves no provider at all, leaving this package's shipped defaults. It is how a host reproduces the palette it had before adopting a provider, which is the first thing to check when colours differ between two environments.
204
+
205
+ ## Token registry
206
+
207
+ `@pie-players/pie-theme/token-registry.json` lists every `--pie-*` token with its
208
+ owner, scope, category, status, fallback policy, and scheme participation
209
+ (`required`, `optional`, or `excluded`). `PieThemeTokenRegistryEntry` types it.
210
+
211
+ It is published so a host can show a person what a token is for and who owns it. Read the registry rather than deriving grouping from token names or keeping a local copy: both drift as soon as a token is added here, and `check:theme-tokens` holds the registry against source on every commit.
212
+
213
+ ## Generated CSS
214
+
215
+ `tokens.css` and `color-schemes.css` are checked-in output adapters. Generation
216
+ is explicit so builds and releases never rewrite tracked source:
217
+
218
+ ```sh
219
+ bun --cwd packages/theme run generate:css
220
+ bun --cwd packages/theme run check:generated-css
221
+ ```
222
+
223
+ The writer is `packages/theme/scripts/generate-theme-css.ts --write`; `--check`
224
+ performs the non-mutating comparison. `bun run check:theme-tokens` also rejects
225
+ stale generated CSS, and the theme package build runs the stale check before it
226
+ copies the existing files to `dist`.
227
+
228
+ The package-internal `renderPieThemeCss()` implementation lives in
229
+ `src/theme-css.ts`; it is not a public export. Generated `color-schemes.css`
230
+ contains one unlayered `[data-color-scheme="..."]` rule per built-in scheme and
231
+ no base-theme or grouped exception rules.
86
232
 
87
233
  ## Light DOM and Shadow DOM
88
234
 
@@ -95,10 +241,11 @@ Then activate with `scheme="district-high-contrast"` on `pie-theme`.
95
241
 
96
242
  Use `@pie-players/pie-theme/components.css` for shared visual styles that are intentionally reused across multiple PIE custom elements.
97
243
 
98
- **Players install this stylesheet themselves; hosts do not import it.** Mounting
99
- `<pie-theme>` does not load it either that element only writes `--pie-*` custom
100
- properties. `@pie-players/pie-item-player` bundles the stylesheet as text and
101
- installs it once per document at import time; see
244
+ `@pie-players/pie-item-player` installs this stylesheet itself, so new hosts do
245
+ not need a second copy. Existing hosts may still own the exported stylesheet;
246
+ that published path remains supported. Mounting `<pie-theme>` does not load it
247
+ the element only writes `--pie-*` custom properties. The item player bundles the
248
+ stylesheet as text and installs it once per document at import time; see
102
249
  [content styles](../item-player/README.md#content-styles) for the host-ownership
103
250
  opt-out.
104
251