@pie-players/pie-theme 0.3.67 → 0.3.69

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.
@@ -24,6 +24,12 @@ export const PIE_THEME_SCHEME_PARTICIPATION = {
24
24
  "--pie-button-hover-bg": "required",
25
25
  "--pie-button-hover-border": "required",
26
26
  "--pie-button-hover-color": "required",
27
+ "--pie-calculator-series-1": "optional",
28
+ "--pie-calculator-series-2": "optional",
29
+ "--pie-calculator-series-3": "optional",
30
+ "--pie-calculator-series-4": "optional",
31
+ "--pie-calculator-series-5": "optional",
32
+ "--pie-calculator-series-6": "optional",
27
33
  "--pie-content-styles": "excluded",
28
34
  "--pie-content-emphasis": "required",
29
35
  "--pie-correct": "required",
@@ -51,11 +57,15 @@ export const PIE_THEME_SCHEME_PARTICIPATION = {
51
57
  "--pie-primary": "required",
52
58
  "--pie-primary-dark": "required",
53
59
  "--pie-primary-light": "required",
60
+ "--pie-scrollbar-thumb": "excluded",
61
+ "--pie-scrollbar-thumb-hover": "excluded",
62
+ "--pie-scrollbar-track": "excluded",
54
63
  "--pie-secondary": "required",
55
64
  "--pie-secondary-background": "required",
56
65
  "--pie-secondary-dark": "required",
57
66
  "--pie-secondary-light": "required",
58
67
  "--pie-section-player-card-header-background": "optional",
68
+ "--pie-section-player-card-header-background-dark": "optional",
59
69
  "--pie-section-player-card-header-radius": "excluded",
60
70
  "--pie-section-player-card-radius": "excluded",
61
71
  "--pie-section-player-item-media-aspect-ratio": "excluded",
@@ -85,5 +95,10 @@ export const PIE_THEME_SCHEME_PARTICIPATION = {
85
95
  "--pie-tool-trigger-active-color": "optional",
86
96
  "--pie-toolbar-tools-row-height": "excluded",
87
97
  "--pie-tts-controls-row-height": "excluded",
98
+ "--pie-tts-line-highlight": "excluded",
99
+ "--pie-tts-sentence-highlight": "excluded",
100
+ "--pie-tts-word-highlight": "excluded",
101
+ "--pie-tts-word-shadow": "excluded",
102
+ "--pie-tts-word-underline": "excluded",
88
103
  "--pie-white": "required",
89
104
  };
@@ -6,6 +6,23 @@ type BuiltInColorSchemeDefinition = Readonly<{
6
6
  description?: string;
7
7
  variables: Readonly<ThemeVariables>;
8
8
  }>;
9
+ /**
10
+ * The CSS `color-scheme` keyword a palette implies, or `null` when the token
11
+ * values cannot decide it.
12
+ *
13
+ * A scheme replaces every colour, but nothing in CSS infers polarity from custom
14
+ * properties, so UA-styled controls -- `input`, `select`, scrollbars, native form
15
+ * widgets -- keep whatever polarity the host's theme declared. A dark
16
+ * accommodation on a light host therefore renders their text in the light-mode
17
+ * system colour: measured at roughly 1.1:1 against `yellow-on-blue`.
18
+ *
19
+ * Decided by whether black or white contrasts better against the resolved
20
+ * background, which is the same test that picks a legible foreground. `null` for
21
+ * a background `parseOpaqueColor` rejects -- a translucent value, a `var()`
22
+ * reference out to a host property, the transparent light base -- because
23
+ * polarity then depends on the host's backdrop and is the host's to declare.
24
+ */
25
+ export declare function resolvePaletteColorScheme(variables: Readonly<ThemeVariables>): "light" | "dark" | null;
9
26
  export declare function diagnoseThemeContrast(variables: Readonly<ThemeVariables>, schemeId?: string, relevantTokens?: ReadonlySet<string>): readonly PieThemeDiagnostic[];
10
27
  export declare function getBaseThemeVariables(baseTheme: "light" | "dark"): Readonly<ThemeVariables>;
11
28
  export declare function listBuiltInColorSchemeDefinitions(): readonly BuiltInColorSchemeDefinition[];
@@ -31,7 +31,7 @@ const LIGHT_BASE_THEME = {
31
31
  "--pie-secondary-dark": "#880e4f",
32
32
  "--pie-tertiary": "#146eb3",
33
33
  "--pie-tertiary-light": "#d0e2f0",
34
- "--pie-background": "rgba(255, 255, 255, 0)",
34
+ "--pie-background": "#ffffff",
35
35
  "--pie-background-dark": "#ecedf1",
36
36
  "--pie-secondary-background": "rgba(241, 241, 241, 1)",
37
37
  "--pie-dropdown-background": "#e0e1e6",
@@ -411,7 +411,7 @@ const BUILT_IN_COLOR_SCHEMES = deepFreeze([
411
411
  "--pie-focus-unchecked": "#ffddee",
412
412
  "--pie-focus-unchecked-border": "#000000",
413
413
  "--pie-blue-grey-100": "#ffeef5",
414
- "--pie-blue-grey-300": "#ffccdd",
414
+ "--pie-blue-grey-300": "#ffb3cc",
415
415
  "--pie-blue-grey-600": "#cc6688",
416
416
  "--pie-blue-grey-900": "#000000",
417
417
  "--pie-tool-annotation-toolbar-border": "var(--pie-border)",
@@ -730,12 +730,7 @@ const DEFAULT_SCHEME_DESCRIPTOR = deepFreeze({
730
730
  name: "Default",
731
731
  description: "Standard PIE colors",
732
732
  kind: "default",
733
- preview: createPieColorSchemePreview({
734
- ...LIGHT_BASE_THEME,
735
- // The live light Base Theme intentionally reveals its host surface. A
736
- // catalog swatch has no such surface, so composite it on white explicitly.
737
- "--pie-background": "#ffffff",
738
- }),
733
+ preview: createPieColorSchemePreview(LIGHT_BASE_THEME),
739
734
  });
740
735
  const REQUIRED_SCHEME_TOKENS = Object.freeze(Object.entries(PIE_THEME_SCHEME_PARTICIPATION)
741
736
  .filter(([, participation]) => participation === "required")
@@ -807,6 +802,17 @@ const PIE_THEME_CONTRAST_RELATIONSHIPS = deepFreeze([
807
802
  minimum: 3,
808
803
  role: "incorrect feedback icon",
809
804
  },
805
+ {
806
+ // The error-surface pair. `--pie-incorrect-secondary` is a tint of the
807
+ // page, about 1.1:1 against it in most schemes, so a banner painted with
808
+ // it reads as a banner only by its `--pie-incorrect` edge -- and the ink
809
+ // on it is the page's own, not the error hue: `--pie-incorrect` against
810
+ // this tint falls to 4.14:1 under Black on White.
811
+ foreground: "--pie-text",
812
+ background: "--pie-incorrect-secondary",
813
+ minimum: 4.5,
814
+ role: "incorrect feedback surface text",
815
+ },
810
816
  {
811
817
  foreground: "--pie-missing-icon",
812
818
  background: "--pie-background",
@@ -904,12 +910,6 @@ function parseOpaqueColor(value) {
904
910
  return { r: 0, g: 0, b: 0 };
905
911
  if (normalized === "white")
906
912
  return { r: 255, g: 255, b: 255 };
907
- if (normalized === "rgba(255, 255, 255, 0)") {
908
- // The transparent light Base Theme is an externally observed behavior. Its
909
- // effective contrast depends on the host backdrop and cannot be certified
910
- // from the token value alone.
911
- return null;
912
- }
913
913
  const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(normalized);
914
914
  if (hex) {
915
915
  const expanded = hex[1].length === 3
@@ -958,6 +958,31 @@ function resolveColor(variables, token, seen = new Set()) {
958
958
  ? resolveColor(variables, reference[1], seen)
959
959
  : value;
960
960
  }
961
+ /**
962
+ * The CSS `color-scheme` keyword a palette implies, or `null` when the token
963
+ * values cannot decide it.
964
+ *
965
+ * A scheme replaces every colour, but nothing in CSS infers polarity from custom
966
+ * properties, so UA-styled controls -- `input`, `select`, scrollbars, native form
967
+ * widgets -- keep whatever polarity the host's theme declared. A dark
968
+ * accommodation on a light host therefore renders their text in the light-mode
969
+ * system colour: measured at roughly 1.1:1 against `yellow-on-blue`.
970
+ *
971
+ * Decided by whether black or white contrasts better against the resolved
972
+ * background, which is the same test that picks a legible foreground. `null` for
973
+ * a background `parseOpaqueColor` rejects -- a translucent value, a `var()`
974
+ * reference out to a host property, the transparent light base -- because
975
+ * polarity then depends on the host's backdrop and is the host's to declare.
976
+ */
977
+ export function resolvePaletteColorScheme(variables) {
978
+ const value = resolveColor(variables, "--pie-background");
979
+ const background = value ? parseOpaqueColor(value) : null;
980
+ if (!background)
981
+ return null;
982
+ const againstWhite = contrastRatio({ r: 255, g: 255, b: 255 }, background);
983
+ const againstBlack = contrastRatio({ r: 0, g: 0, b: 0 }, background);
984
+ return againstWhite > againstBlack ? "dark" : "light";
985
+ }
961
986
  export function diagnoseThemeContrast(variables, schemeId, relevantTokens) {
962
987
  const diagnostics = [];
963
988
  for (const relationship of PIE_THEME_CONTRAST_RELATIONSHIPS) {
@@ -1063,10 +1088,7 @@ function validateBuiltInDefinitions() {
1063
1088
  function validateBaseThemes() {
1064
1089
  const failures = [];
1065
1090
  for (const baseTheme of ["light", "dark"]) {
1066
- const measurableVariables = baseTheme === "light"
1067
- ? { ...BASE_THEMES.light, "--pie-background": "#ffffff" }
1068
- : BASE_THEMES.dark;
1069
- for (const diagnostic of diagnoseThemeContrast(measurableVariables, `${baseTheme}-base`)) {
1091
+ for (const diagnostic of diagnoseThemeContrast(BASE_THEMES[baseTheme], `${baseTheme}-base`)) {
1070
1092
  failures.push(`${baseTheme}: ${diagnostic.message}`);
1071
1093
  }
1072
1094
  }
@@ -28,6 +28,22 @@ export declare class PieThemeElement extends HTMLElementBase {
28
28
  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
29
29
  protected getTarget(): HTMLElement;
30
30
  protected applyTheme(): void;
31
+ /**
32
+ * Provider adapters resolve by reading custom properties off the target, and
33
+ * those are selected by the target's own `data-theme` -- daisyUI's `--color-*`
34
+ * are the case that matters. So the target has to be carrying the incoming
35
+ * theme before the read: resolving first resolves the palette of the theme
36
+ * being replaced and leaves every `--pie-*` value one selection behind, with
37
+ * nothing to re-resolve it until some other attribute changes.
38
+ *
39
+ * The attributes are restored rather than left in place. For
40
+ * `scope="document"` the ownership arbitration in `applyTheme` decides whether
41
+ * this element may stamp them at all, and `ensureDocumentThemeBaseline` has to
42
+ * see the host's pre-existing value to have something to restore on
43
+ * disconnect. Stamp and restore happen in one synchronous pass, so the
44
+ * transient state is never painted.
45
+ */
46
+ private resolveProviderVariablesForState;
31
47
  private resolveThemeState;
32
48
  private clearPreviousTarget;
33
49
  private clearPreviousKeys;
@@ -48,6 +48,31 @@ function parseVariableOverrides(value) {
48
48
  }
49
49
  return output;
50
50
  }
51
+ /**
52
+ * `color-scheme` decides how UA-styled controls paint -- `input` and `select`
53
+ * text, scrollbars, native form widgets -- and no amount of custom properties
54
+ * reaches them. A scheme that replaces the palette without it leaves those
55
+ * controls on the host theme's polarity, which under a dark accommodation on a
56
+ * light host means dark-on-dark.
57
+ *
58
+ * `null` restores rather than removes. Clearing a scheme has to undo this
59
+ * element's keyword, and the thing to return to is whatever the host declared --
60
+ * removing outright would take a host's own `color-scheme` away the first time a
61
+ * document-scoped element resolved without a scheme, which is most of the time.
62
+ * A self-scoped target has no host declaration to preserve, so its caller passes
63
+ * no baseline and `null` does remove.
64
+ */
65
+ function applyColorScheme(target, colorScheme, baseline) {
66
+ if (colorScheme) {
67
+ target.style.setProperty("color-scheme", colorScheme);
68
+ }
69
+ else if (baseline) {
70
+ target.style.setProperty("color-scheme", baseline.value, baseline.priority);
71
+ }
72
+ else {
73
+ target.style.removeProperty("color-scheme");
74
+ }
75
+ }
51
76
  const documentThemeOwners = new Map();
52
77
  let documentThemeBaseline = null;
53
78
  let documentThemeAppliedKeys = new Set();
@@ -55,6 +80,12 @@ function ensureDocumentThemeBaseline(target, tokens) {
55
80
  documentThemeBaseline ??= {
56
81
  dataTheme: target.getAttribute("data-theme"),
57
82
  dataColorScheme: target.getAttribute("data-color-scheme"),
83
+ colorScheme: target.style.getPropertyValue("color-scheme")
84
+ ? {
85
+ value: target.style.getPropertyValue("color-scheme"),
86
+ priority: target.style.getPropertyPriority("color-scheme"),
87
+ }
88
+ : null,
58
89
  variables: new Map(),
59
90
  };
60
91
  for (const token of tokens) {
@@ -92,6 +123,7 @@ function applyDocumentThemeState(target, state) {
92
123
  else {
93
124
  target.removeAttribute("data-color-scheme");
94
125
  }
126
+ applyColorScheme(target, state.colorScheme, documentThemeBaseline?.colorScheme ?? null);
95
127
  for (const [token, value] of Object.entries(state.variables)) {
96
128
  target.style.setProperty(token, value);
97
129
  }
@@ -111,6 +143,12 @@ function restoreDocumentThemeBaseline(target) {
111
143
  else {
112
144
  target.setAttribute("data-color-scheme", baseline.dataColorScheme);
113
145
  }
146
+ if (baseline.colorScheme) {
147
+ target.style.setProperty("color-scheme", baseline.colorScheme.value, baseline.colorScheme.priority);
148
+ }
149
+ else {
150
+ target.style.removeProperty("color-scheme");
151
+ }
114
152
  restoreDocumentThemeVariables(target);
115
153
  documentThemeBaseline = null;
116
154
  documentThemeAppliedKeys.clear();
@@ -209,10 +247,8 @@ export class PieThemeElement extends HTMLElementBase {
209
247
  if (this.previousTarget && this.previousTarget !== target) {
210
248
  this.clearPreviousTarget();
211
249
  }
212
- const providerVariables = resolveProviderVariables({
213
- target,
214
- provider: this.provider,
215
- });
250
+ const dataColorScheme = this.scheme === "default" ? null : this.scheme;
251
+ const providerVariables = this.resolveProviderVariablesForState(target, dataTheme, dataColorScheme);
216
252
  const resolution = resolvePieTheme({
217
253
  baseTheme: effectiveTheme,
218
254
  providerVariables,
@@ -221,7 +257,8 @@ export class PieThemeElement extends HTMLElementBase {
221
257
  });
222
258
  const state = Object.freeze({
223
259
  dataTheme,
224
- dataColorScheme: this.scheme === "default" ? null : this.scheme,
260
+ dataColorScheme,
261
+ colorScheme: resolution.colorScheme,
225
262
  variables: resolution.variables,
226
263
  });
227
264
  if (this.scope === "document") {
@@ -245,6 +282,7 @@ export class PieThemeElement extends HTMLElementBase {
245
282
  else {
246
283
  target.removeAttribute("data-color-scheme");
247
284
  }
285
+ applyColorScheme(target, state.colorScheme, null);
248
286
  this.clearPreviousKeys(target);
249
287
  for (const [key, value] of Object.entries(resolution.variables)) {
250
288
  target.style.setProperty(key, value);
@@ -253,6 +291,40 @@ export class PieThemeElement extends HTMLElementBase {
253
291
  this.previousTarget = target;
254
292
  this.previousKeys = new Set(Object.keys(resolution.variables));
255
293
  }
294
+ /**
295
+ * Provider adapters resolve by reading custom properties off the target, and
296
+ * those are selected by the target's own `data-theme` -- daisyUI's `--color-*`
297
+ * are the case that matters. So the target has to be carrying the incoming
298
+ * theme before the read: resolving first resolves the palette of the theme
299
+ * being replaced and leaves every `--pie-*` value one selection behind, with
300
+ * nothing to re-resolve it until some other attribute changes.
301
+ *
302
+ * The attributes are restored rather than left in place. For
303
+ * `scope="document"` the ownership arbitration in `applyTheme` decides whether
304
+ * this element may stamp them at all, and `ensureDocumentThemeBaseline` has to
305
+ * see the host's pre-existing value to have something to restore on
306
+ * disconnect. Stamp and restore happen in one synchronous pass, so the
307
+ * transient state is never painted.
308
+ */
309
+ resolveProviderVariablesForState(target, dataTheme, dataColorScheme) {
310
+ const setOrRemove = (name, value) => {
311
+ if (value === null)
312
+ target.removeAttribute(name);
313
+ else
314
+ target.setAttribute(name, value);
315
+ };
316
+ const previousTheme = target.getAttribute("data-theme");
317
+ const previousColorScheme = target.getAttribute("data-color-scheme");
318
+ setOrRemove("data-theme", dataTheme);
319
+ setOrRemove("data-color-scheme", dataColorScheme);
320
+ try {
321
+ return resolveProviderVariables({ target, provider: this.provider });
322
+ }
323
+ finally {
324
+ setOrRemove("data-theme", previousTheme);
325
+ setOrRemove("data-color-scheme", previousColorScheme);
326
+ }
327
+ }
256
328
  resolveThemeState() {
257
329
  const rawTheme = this.getAttribute("theme")?.trim();
258
330
  if (rawTheme === "auto") {
@@ -286,6 +358,9 @@ export class PieThemeElement extends HTMLElementBase {
286
358
  this.clearPreviousKeys(target);
287
359
  target.removeAttribute("data-theme");
288
360
  target.removeAttribute("data-color-scheme");
361
+ // Self-scoped targets are the element itself, so anything written here
362
+ // was written by this element -- no host baseline to preserve.
363
+ target.style.removeProperty("color-scheme");
289
364
  }
290
365
  this.previousTarget = null;
291
366
  this.previousKeys.clear();
@@ -41,6 +41,11 @@ export type ThemeResolution = Readonly<{
41
41
  resolvedScheme: PieColorSchemeDescriptor | null;
42
42
  status: PieThemeResolutionStatus;
43
43
  variables: Readonly<ThemeVariables>;
44
+ /**
45
+ * The CSS `color-scheme` keyword this resolution implies, or `null` when the
46
+ * host keeps ownership of it -- which is every resolution without a scheme.
47
+ */
48
+ colorScheme: "light" | "dark" | null;
44
49
  diagnostics: readonly PieThemeDiagnostic[];
45
50
  }>;
46
51
  export type RegisteredPieColorScheme = Readonly<{