@multiplatform.one/theme 7.28.0 → 7.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/theme",
3
- "version": "7.28.0",
3
+ "version": "7.29.0",
4
4
  "description": "Tamagui theme system for multiplatform.one",
5
5
  "keywords": [
6
6
  "multiplatform",
@@ -44,8 +44,8 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@multiplatform.one/platform": "7.28.0",
48
- "@multiplatform.one/store": "7.28.0",
47
+ "@multiplatform.one/platform": "7.29.0",
48
+ "@multiplatform.one/store": "7.29.0",
49
49
  "@tamagui/animations-react-native": "2.7.6",
50
50
  "@tamagui/colors": "2.7.6",
51
51
  "@tamagui/config": "2.7.6",
@@ -58,8 +58,8 @@
58
58
  "react-cookie": "^8.1.2"
59
59
  },
60
60
  "devDependencies": {
61
- "@multiplatform.one/config": "7.28.0",
62
- "@multiplatform.one/test-utils": "7.28.0",
61
+ "@multiplatform.one/config": "7.29.0",
62
+ "@multiplatform.one/test-utils": "7.29.0",
63
63
  "@tamagui/animations-css": "2.7.6",
64
64
  "@tamagui/animations-reanimated": "2.7.6",
65
65
  "@tamagui/font-inter": "2.7.6",
@@ -0,0 +1,56 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { contrastRatio, normalizeToHex, relativeLuminance } from "./colorRules";
3
+ import { createThemesBuilder } from "./createThemes";
4
+ import { defaultAccentTheme } from "./defaults/accent";
5
+ import { defaultBaseTheme } from "./defaults/base";
6
+ import { defaultBuilderOptions } from "./defaults/builderOptions";
7
+
8
+ const themes = createThemesBuilder(
9
+ defaultBaseTheme,
10
+ defaultAccentTheme,
11
+ defaultBuilderOptions,
12
+ ).themes() as Record<string, Record<string, string>>;
13
+
14
+ const hex = (raw: string) => {
15
+ const value = normalizeToHex(raw);
16
+ if (!value) throw new Error(`not an opaque colour: ${raw}`);
17
+ return value;
18
+ };
19
+ const ratio = (a: string, b: string) =>
20
+ contrastRatio(relativeLuminance(hex(a)), relativeLuminance(hex(b)));
21
+
22
+ describe("MPO-448 / MPO-449: the built accent pair holds Axiom 12", () => {
23
+ for (const scheme of ["light", "dark"] as const) {
24
+ const theme = themes[scheme];
25
+
26
+ it(`${scheme} $accentBackground carries AA text with its better anchor and 3:1 on the page`, () => {
27
+ const fill = theme.accentBackground;
28
+ expect(
29
+ Math.max(ratio(fill, theme.color1), ratio(fill, theme.color12)),
30
+ ).toBeGreaterThanOrEqual(4.5);
31
+ expect(ratio(fill, theme.color1)).toBeGreaterThanOrEqual(3);
32
+ });
33
+
34
+ it(`${scheme} $accentColor reads at 4.5:1 on $color1 through $color3`, () => {
35
+ for (const surface of [theme.color1, theme.color2, theme.color3]) {
36
+ expect(ratio(theme.accentColor, surface)).toBeGreaterThanOrEqual(4.5);
37
+ }
38
+ });
39
+ }
40
+
41
+ it("light keeps the builder's pair; dark takes the nearest steps that hold", () => {
42
+ expect(hex(themes.light.accentBackground)).toBe("#634fc4");
43
+ expect(hex(themes.light.accentColor)).toBe("#3d2e8a");
44
+ expect(themes.dark.accentBackground).toBe(themes.dark.accent9);
45
+ expect(themes.dark.accentColor).toBe(themes.dark.accent11);
46
+ });
47
+
48
+ it("the intent children carry the corrected pair and the inverse component themes keep theirs", () => {
49
+ for (const name of ["dark_warning", "dark_error", "dark_success"]) {
50
+ expect(themes[name].accentBackground).toBe(themes.dark.accentBackground);
51
+ expect(themes[name].accentColor).toBe(themes.dark.accentColor);
52
+ }
53
+ expect(hex(themes.dark_Tooltip.accentBackground)).toBe("#46349d");
54
+ expect(hex(themes.dark_Tooltip.accentColor)).toBe("#7766cc");
55
+ });
56
+ });
@@ -1,7 +1,7 @@
1
1
  import * as Colors from "@tamagui/colors";
2
2
  import { themes as stockThemes } from "@tamagui/themes";
3
3
  import { describe, expect, it } from "vitest";
4
- import { resolveChartPalette } from "./chartPalette";
4
+ import { accentIdentityCandidates, resolveChartPalette } from "./chartPalette";
5
5
  import { contrastRatio, normalizeToHex, relativeLuminance } from "./colorRules";
6
6
  import { tintHueNames } from "./createThemes";
7
7
 
@@ -280,3 +280,29 @@ describe("resolveChartPalette", () => {
280
280
  expect(a).toEqual(b);
281
281
  });
282
282
  });
283
+
284
+ describe("chart identity under the held dark accent (MPO-448)", () => {
285
+ const darkAccentSolids = [35, 38, 41, 43, 46, 49, 52, 54, 57, 60].map(
286
+ (lightness) => `hsla(250, 50%, ${lightness}%, 1)`,
287
+ );
288
+
289
+ it("keeps the accent channel on the neighbouring step when the held fill misses the mark floor", () => {
290
+ const identitySolid = darkAccentSolids[8];
291
+ const palette = resolveChartPalette({
292
+ scheme: "dark",
293
+ identitySolid,
294
+ identityCandidates: [
295
+ ...accentIdentityCandidates(identitySolid, darkAccentSolids),
296
+ "hsla(270, 6%, 54%, 1)",
297
+ ],
298
+ });
299
+ expect(palette.single).toBe(darkAccentSolids[9]);
300
+ });
301
+
302
+ it("orders the accent solids nearest-first and skips what does not parse", () => {
303
+ expect(
304
+ accentIdentityCandidates("#6d5bc8", ["#3c2d86", undefined, "#7766cc", "#634fc4"]),
305
+ ).toEqual(["#634fc4", "#7766cc", "#3c2d86"]);
306
+ expect(accentIdentityCandidates(undefined, ["#7766cc"])).toEqual([]);
307
+ });
308
+ });
@@ -226,6 +226,31 @@ export function resolveChartPalette(input: ResolveChartPaletteInput): ChartPalet
226
226
  };
227
227
  }
228
228
 
229
+ /**
230
+ * The accent solids ordered nearest-first in luminance to `identity`, so a
231
+ * chart identity that misses the 3:1 mark floor on the reference cards falls
232
+ * to the next accent step before a neutral one. `$accentBackground` is held
233
+ * to the text floor (MPO-448), which in dark lands it one step deeper than
234
+ * the marks need; the chart keeps the accent channel on the step beside it.
235
+ */
236
+ export function accentIdentityCandidates(
237
+ identity: string | undefined,
238
+ solids: readonly (string | undefined)[],
239
+ ): string[] {
240
+ const identityHex = identity ? normalizeToHex(identity) : null;
241
+ if (!identityHex) return [];
242
+ const identityLum = relativeLuminance(identityHex);
243
+ return solids
244
+ .flatMap((solid) => {
245
+ const hex = solid ? normalizeToHex(solid) : null;
246
+ return solid && hex
247
+ ? [{ solid, distance: Math.abs(relativeLuminance(hex) - identityLum) }]
248
+ : [];
249
+ })
250
+ .sort((a, b) => a.distance - b.distance)
251
+ .map(({ solid }) => solid);
252
+ }
253
+
229
254
  /**
230
255
  * Canonical chart palette for the active Tamagui theme.
231
256
  *
@@ -246,8 +271,20 @@ export function useChartPalette(): ChartPalette {
246
271
  const identitySolid = isTint ? (color9 ?? accent) : (accent ?? color9);
247
272
  const color10 = theme.color10?.val;
248
273
  const color11 = theme.color11?.val;
274
+ const accentSolids = Array.from({ length: 10 }, (_, index) => theme[`accent${index + 1}`]?.val);
275
+ const accentKey = accentSolids.join("|");
249
276
  return useMemo(
250
- () => resolveChartPalette({ scheme, identitySolid, identityCandidates: [color10, color11] }),
251
- [scheme, identitySolid, color10, color11],
277
+ () =>
278
+ resolveChartPalette({
279
+ scheme,
280
+ identitySolid,
281
+ identityCandidates: [
282
+ ...(isTint ? [] : accentIdentityCandidates(identitySolid, accentSolids)),
283
+ color10,
284
+ color11,
285
+ ],
286
+ }),
287
+ // eslint-disable-next-line react-hooks/exhaustive-deps
288
+ [scheme, identitySolid, isTint, accentKey, color10, color11],
252
289
  );
253
290
  }
@@ -4,6 +4,7 @@ import {
4
4
  assertContrast,
5
5
  contrastRatio,
6
6
  findReadableStep,
7
+ holdInkFloor,
7
8
  measureContrast,
8
9
  minContrastRatio,
9
10
  normalizeColorScale,
@@ -551,3 +552,31 @@ describe("intent color11 on the base page surfaces (MPO-40)", () => {
551
552
  ]);
552
553
  });
553
554
  });
555
+
556
+ describe("holdInkFloor (MPO-448)", () => {
557
+ const onFill = (ink: string, fill: string) =>
558
+ contrastRatio(
559
+ relativeLuminance(normalizeToHex(ink)!),
560
+ relativeLuminance(normalizeToHex(fill)!),
561
+ );
562
+
563
+ it("keeps an ink that already clears the floor", () => {
564
+ expect(holdInkFloor("#6d5bc8", "#f0eff1")).toBe("#f0eff1");
565
+ });
566
+
567
+ it("hands a literal bar the pole when both dark anchors miss (#cc4e00: 4.03 / 3.93)", () => {
568
+ const ink = holdInkFloor("#cc4e00", "#161519");
569
+ expect(ink).toBe("#000000");
570
+ expect(onFill(ink, "#cc4e00")).toBeGreaterThanOrEqual(aaTextContrastRatio);
571
+ });
572
+
573
+ it("hands a dark step-8 chip the white pole over its step-12 ink (3.88:1)", () => {
574
+ const ink = holdInkFloor("#2870bd", "#c2e6ff");
575
+ expect(ink).toBe("#ffffff");
576
+ expect(onFill(ink, "#2870bd")).toBeGreaterThanOrEqual(aaTextContrastRatio);
577
+ });
578
+
579
+ it("returns the ink untouched when a colour cannot be parsed", () => {
580
+ expect(holdInkFloor("var(--x)", "#161519")).toBe("#161519");
581
+ });
582
+ });
@@ -217,6 +217,28 @@ export function pickReadableForeground(
217
217
  : candidateB;
218
218
  }
219
219
 
220
+ /**
221
+ * Axiom 12 backstop for ink on a fill: `ink` when it clears `floor` on
222
+ * `fill`, otherwise the pole (`#000000` / `#ffffff`) that reads better on it,
223
+ * when that pole beats `ink`. A mid-luminance fill, such as a literal
224
+ * `#cc4e00` bar or a Radix step-8 chip in dark, can sit where neither scheme
225
+ * anchor clears AA while a pole does. Returns `ink` unchanged when a color
226
+ * fails to parse.
227
+ */
228
+ export function holdInkFloor(fill: string, ink: string, floor = aaTextContrastRatio): string {
229
+ const fillHex = normalizeToHex(fill);
230
+ const inkHex = normalizeToHex(ink);
231
+ if (!fillHex || !inkHex) return ink;
232
+ const fillLum = relativeLuminance(fillHex);
233
+ const inkRatio = contrastRatio(relativeLuminance(inkHex), fillLum);
234
+ if (inkRatio >= floor) return ink;
235
+ const onWhite = contrastRatio(1, fillLum);
236
+ const onBlack = contrastRatio(0, fillLum);
237
+ const poleRatio = Math.max(onWhite, onBlack);
238
+ if (poleRatio <= inkRatio) return ink;
239
+ return onWhite >= onBlack ? "#ffffff" : "#000000";
240
+ }
241
+
220
242
  /**
221
243
  * Given a 12-color scale (hex strings), a background step, and a starting
222
244
  * text step, scan towards `preferredDir` for the first step whose contrast
@@ -4,6 +4,13 @@ import {
4
4
  type CreateThemesProps,
5
5
  } from "@tamagui/theme-builder";
6
6
  import { themes as tamaguiThemes } from "@tamagui/themes";
7
+ import {
8
+ aaTextContrastRatio,
9
+ contrastRatio,
10
+ minContrastRatio,
11
+ normalizeToHex,
12
+ relativeLuminance,
13
+ } from "./colorRules";
7
14
 
8
15
  /**
9
16
  * MPO-40. Radix aims a scale's step 11 at the AA text floor against ITS OWN
@@ -245,6 +252,147 @@ function neutralizeSemanticEntryText(
245
252
  return out;
246
253
  }
247
254
 
255
+ function luminanceOf(color: string | undefined): number | null {
256
+ const hex = color ? normalizeToHex(color) : null;
257
+ return hex ? relativeLuminance(hex) : null;
258
+ }
259
+
260
+ /**
261
+ * The accent step nearest in luminance to `preferred` that passes `holds`,
262
+ * or `preferred` itself when it already passes or nothing does.
263
+ */
264
+ function nearestHoldingStep(
265
+ preferred: string,
266
+ steps: (string | undefined)[],
267
+ holds: (lum: number) => boolean,
268
+ ): string {
269
+ const preferredLum = luminanceOf(preferred);
270
+ if (preferredLum === null || holds(preferredLum)) return preferred;
271
+ let best = preferred;
272
+ let bestDistance = Number.POSITIVE_INFINITY;
273
+ for (const step of steps) {
274
+ const lum = luminanceOf(step);
275
+ if (step === undefined || lum === null || !holds(lum)) continue;
276
+ const distance = Math.abs(lum - preferredLum);
277
+ if (distance < bestDistance) {
278
+ bestDistance = distance;
279
+ best = step;
280
+ }
281
+ }
282
+ return best;
283
+ }
284
+
285
+ /**
286
+ * Axiom 12 LEGIBLE FLOOR on the scheme's accent pair (MPO-448, MPO-449). The
287
+ * theme-builder reads `accentBackground` and `accentColor` off fixed palette
288
+ * slots, and the house dark accent ramp puts both on the wrong side of the
289
+ * floor: the fill (`#7766cc`) carries text at 4.02:1 against either scheme
290
+ * anchor, and the ink (`#46349d`) reads 1.74:1 on the dark page. LC-05 keeps
291
+ * the channel and frees the step, so each token walks to the nearest accent
292
+ * step that holds its floor and stays put where it already does (light).
293
+ *
294
+ * - `accentBackground` is a text-bearing solid: its better anchor (`$color1`
295
+ * or `$color12`) clears 4.5:1 on it, and it keeps the 3:1 graphic tier
296
+ * against the page, because dots, edges and bars paint it bare.
297
+ * - `accentColor` is accent ink: 4.5:1 on the page and the first raised
298
+ * surface (`$color1`-`$color3`).
299
+ *
300
+ * Every theme of the scheme that inherited the base pair (the intent
301
+ * children) takes the corrected pair; one that set its own (the inverse
302
+ * component themes) keeps it.
303
+ */
304
+ function holdAccentFloor(
305
+ themes: Record<string, Record<string, string>>,
306
+ ): Record<string, Record<string, string>> {
307
+ const out: Record<string, Record<string, string>> = { ...themes };
308
+ for (const scheme of ["light", "dark"] as const) {
309
+ const base = themes[scheme];
310
+ if (!base?.accentBackground || !base.accentColor) continue;
311
+ const paper = luminanceOf(base.color1);
312
+ const ink = luminanceOf(base.color12);
313
+ const surfaces = [base.color1, base.color2, base.color3].map(luminanceOf);
314
+ if (paper === null || ink === null || surfaces.some((lum) => lum === null)) continue;
315
+ const steps = Array.from({ length: 12 }, (_, index) => base[`accent${index + 1}`]);
316
+ const fill = nearestHoldingStep(
317
+ base.accentBackground,
318
+ steps.slice(0, 10),
319
+ (lum) =>
320
+ Math.max(contrastRatio(lum, paper), contrastRatio(lum, ink)) >= aaTextContrastRatio &&
321
+ contrastRatio(lum, paper) >= minContrastRatio,
322
+ );
323
+ const accentInk = nearestHoldingStep(base.accentColor, steps, (lum) =>
324
+ surfaces.every((surface) => contrastRatio(lum, surface as number) >= aaTextContrastRatio),
325
+ );
326
+ if (fill === base.accentBackground && accentInk === base.accentColor) continue;
327
+ for (const [name, theme] of Object.entries(themes)) {
328
+ if (name !== scheme && !name.startsWith(`${scheme}_`)) continue;
329
+ const inheritsFill = theme.accentBackground === base.accentBackground;
330
+ const inheritsInk = theme.accentColor === base.accentColor;
331
+ if (!inheritsFill && !inheritsInk) continue;
332
+ out[name] = {
333
+ ...theme,
334
+ ...(inheritsFill ? { accentBackground: fill } : null),
335
+ ...(inheritsInk ? { accentColor: accentInk } : null),
336
+ };
337
+ }
338
+ }
339
+ return out;
340
+ }
341
+
342
+ /** The hues the code syntax palette paints (MPO-468). */
343
+ const syntaxInkHues = ["purple", "green", "blue", "orange", "red"] as const;
344
+
345
+ function mixHex(from: string, to: string, share: number): string {
346
+ const channel = (offset: number) =>
347
+ Math.round(
348
+ Number.parseInt(from.slice(offset, offset + 2), 16) * (1 - share) +
349
+ Number.parseInt(to.slice(offset, offset + 2), 16) * share,
350
+ )
351
+ .toString(16)
352
+ .padStart(2, "0");
353
+ return `#${channel(1)}${channel(3)}${channel(5)}`;
354
+ }
355
+
356
+ /**
357
+ * Axiom 12 LEGIBLE FLOOR on code syntax ink (MPO-468). Radix aims a hue's
358
+ * step 11 at 4.5:1 on its own steps 1-2, but code paints on the raised
359
+ * `$color3`, where light `green11`, `blue11` and `orange11` measure 4.22,
360
+ * 4.27 and 4.04:1. Every theme that carries the hue ramps gets a
361
+ * `syntax<Hue>` ink: step 11, walked toward step 12 in twentieths until it
362
+ * clears 4.5:1 on that theme's `$color3` and on the hue's step-3 wash, which
363
+ * diff lines paint. Dark keeps step 11; a theme where no step holds gets no
364
+ * token and inherits its parent's.
365
+ */
366
+ function holdSyntaxFloor(
367
+ themes: Record<string, Record<string, string>>,
368
+ ): Record<string, Record<string, string>> {
369
+ const out: Record<string, Record<string, string>> = { ...themes };
370
+ for (const [name, theme] of Object.entries(themes)) {
371
+ const surface = luminanceOf(theme.color3);
372
+ if (surface === null) continue;
373
+ const ink: Record<string, string> = {};
374
+ for (const hue of syntaxInkHues) {
375
+ const from = theme[`${hue}11`] ? normalizeToHex(theme[`${hue}11`]) : null;
376
+ const to = theme[`${hue}12`] ? normalizeToHex(theme[`${hue}12`]) : null;
377
+ const wash = luminanceOf(theme[`${hue}3`]);
378
+ if (!from || !to || wash === null) continue;
379
+ for (let step = 0; step <= 20; step++) {
380
+ const candidate = mixHex(from, to, step / 20);
381
+ const lum = relativeLuminance(candidate);
382
+ if (
383
+ contrastRatio(lum, surface) >= aaTextContrastRatio &&
384
+ contrastRatio(lum, wash) >= aaTextContrastRatio
385
+ ) {
386
+ ink[`syntax${hue[0].toUpperCase()}${hue.slice(1)}`] = candidate;
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ if (Object.keys(ink).length > 0) out[name] = { ...theme, ...ink };
392
+ }
393
+ return out;
394
+ }
395
+
248
396
  const defaultLightShadows: Shadows = {
249
397
  shadow1: "rgba(0,0,0,0.12)",
250
398
  shadow2: "rgba(0,0,0,0.16)",
@@ -436,12 +584,16 @@ export function createThemesBuilder(
436
584
  const initialTheme = tamaguiCreateThemes(
437
585
  createThemesProps as Parameters<typeof tamaguiCreateThemes>[0],
438
586
  );
439
- const themes = neutralizeSemanticEntryText(
440
- neutralizeTintText({
441
- ...grayTheme,
442
- ...tamaguiThemes,
443
- ...initialTheme,
444
- }),
587
+ const themes = holdSyntaxFloor(
588
+ holdAccentFloor(
589
+ neutralizeSemanticEntryText(
590
+ neutralizeTintText({
591
+ ...grayTheme,
592
+ ...tamaguiThemes,
593
+ ...initialTheme,
594
+ }),
595
+ ),
596
+ ),
445
597
  );
446
598
  return {
447
599
  themes() {
@@ -0,0 +1,91 @@
1
+ /**
2
+ * MPO-463: the panel marks a knob "set" from its own session state, so every
3
+ * remount showed every knob unset while the knobs stayed applied. core's
4
+ * TanstackDevtools keys the shell on the resolved scheme, so a Scheme click
5
+ * remounts it; a reload does the same. The applied knobs live in the
6
+ * `mp.ov.*` cookies, so a fresh mount must read its markers back from them.
7
+ */
8
+ import { cleanup, fireEvent, render, screen, within } from "@testing-library/react";
9
+ import { CookiesProvider } from "react-cookie";
10
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
11
+ import type { ThemeName } from "@tamagui/web";
12
+ import { cookieOverridesPrefix, cookiePreset, persistPresetOverrides } from "../cookies";
13
+ import { defaultKnobs } from "../knobs";
14
+ import { clearPresets, registerPreset } from "../shared";
15
+ import { ThemeDevtoolsPanel } from "./ThemeDevtoolsPanel";
16
+
17
+ function clearThemeCookies() {
18
+ for (const key of [
19
+ cookiePreset,
20
+ ...Array.from({ length: 10 }, (_, i) => `${cookieOverridesPrefix}.${i}`),
21
+ ]) {
22
+ document.cookie = `${key}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
23
+ }
24
+ }
25
+
26
+ function Shell({ scheme }: { scheme: "light" | "dark" }) {
27
+ return (
28
+ <CookiesProvider>
29
+ <ThemeDevtoolsPanel key={scheme} devtoolsTheme={scheme} />
30
+ </CookiesProvider>
31
+ );
32
+ }
33
+
34
+ function pressed(label: string, title: string) {
35
+ return within(screen.getByRole("group", { name: label }))
36
+ .getByTitle(title)
37
+ .getAttribute("aria-pressed");
38
+ }
39
+
40
+ beforeEach(clearThemeCookies);
41
+ afterEach(() => {
42
+ cleanup();
43
+ clearThemeCookies();
44
+ clearPresets();
45
+ });
46
+
47
+ describe("ThemeDevtoolsPanel set markers survive a remount (MPO-463)", () => {
48
+ it("a reload over mp.ov.0 marks the persisted knob set and leaves the rest unset", () => {
49
+ persistPresetOverrides({ fillStyle: "outlined" });
50
+ render(<Shell scheme="light" />);
51
+ expect(pressed("Fill", "outlined")).toBe("true");
52
+ expect(pressed("Fill", "unset (inherit)")).toBe("false");
53
+ expect(pressed("Size", "unset (inherit)")).toBe("true");
54
+ expect(pressed("Radius", "unset (inherit)")).toBe("true");
55
+ });
56
+
57
+ it("a knob set in the panel is still marked after the shell remounts it", () => {
58
+ const shell = render(<Shell scheme="light" />);
59
+ fireEvent.click(within(screen.getByRole("group", { name: "Fill" })).getByTitle("outlined"));
60
+ expect(pressed("Fill", "outlined")).toBe("true");
61
+
62
+ shell.rerender(<Shell scheme="dark" />);
63
+ expect(pressed("Fill", "outlined")).toBe("true");
64
+ expect(pressed("Fill", "unset (inherit)")).toBe("false");
65
+ });
66
+
67
+ it("a preset's own values read as inherited, only the knobs over it as set", () => {
68
+ registerPreset("mpo463", {
69
+ theme: "gray" as ThemeName,
70
+ knobs: { ...defaultKnobs, fillStyle: "outlined", size: "small" },
71
+ intents: {},
72
+ tints: [],
73
+ });
74
+ document.cookie = `${cookiePreset}=mpo463; path=/`;
75
+ persistPresetOverrides({ size: "large" });
76
+ render(<Shell scheme="light" />);
77
+ expect(pressed("Fill", "unset (inherit)")).toBe("true");
78
+ expect(pressed("Size", "large")).toBe("true");
79
+ expect(pressed("Size", "unset (inherit)")).toBe("false");
80
+ });
81
+
82
+ it("a knob never set shows unset after a remount", () => {
83
+ const shell = render(<Shell scheme="light" />);
84
+ fireEvent.click(within(screen.getByRole("group", { name: "Fill" })).getByTitle("outlined"));
85
+
86
+ shell.rerender(<Shell scheme="dark" />);
87
+ for (const label of ["Size", "Space", "Radius", "Border", "Elevation"]) {
88
+ expect(pressed(label, "unset (inherit)"), label).toBe("true");
89
+ }
90
+ });
91
+ });
@@ -72,8 +72,8 @@ import {
72
72
  FormAutofocus,
73
73
  interactionStates,
74
74
  } from "../knobs";
75
- import { animationNames } from "../animations/index";
76
75
  import { getPresetNames, getPreset } from "../shared";
76
+ import { extractAnimationNames } from "./animationOptions";
77
77
  import { ColorLineVisualizer } from "./ColorLineVisualizer";
78
78
  import { buildSnapshot } from "./buildSnapshot";
79
79
  import {
@@ -100,16 +100,6 @@ import {
100
100
  formAutofocusIcon as _formAutofocusIcon,
101
101
  } from "./knobIcons";
102
102
 
103
- function extractAnimationNames(tamaguiConfig: unknown): string[] {
104
- const config = tamaguiConfig as Record<string, unknown> | null;
105
- const anims = config?.animations as Record<string, unknown> | undefined;
106
- if (anims) {
107
- const keys = Object.keys(anims).filter((key) => key !== "animations" && !key.startsWith("_"));
108
- if (keys.length > 0) return keys;
109
- }
110
- return animationNames as string[];
111
- }
112
-
113
103
  // ---------------------------------------------------------------------------
114
104
  // Constants
115
105
  // ---------------------------------------------------------------------------
@@ -573,6 +563,8 @@ function IconGroup<V extends string | number | boolean>({
573
563
  }) {
574
564
  return (
575
565
  <div
566
+ role="group"
567
+ aria-label={label}
576
568
  style={{ ...styles.card, position: "relative" as const }}
577
569
  onMouseEnter={(e) => {
578
570
  const element = (e.currentTarget as HTMLElement).querySelector(
@@ -606,6 +598,7 @@ function IconGroup<V extends string | number | boolean>({
606
598
  }}
607
599
  onClick={onUnset}
608
600
  title="unset (inherit)"
601
+ aria-pressed={value === undefined}
609
602
  >
610
603
  ↺
611
604
  </button>
@@ -619,6 +612,7 @@ function IconGroup<V extends string | number | boolean>({
619
612
  style={styles.iconBtn(active)}
620
613
  onClick={() => onChange(opt)}
621
614
  title={String(opt)}
615
+ aria-pressed={active}
622
616
  >
623
617
  {icon(opt, active, isDark)}
624
618
  </button>
@@ -901,8 +895,16 @@ export function ThemeDevtoolsPanel({
901
895
  [tamaguiConfig],
902
896
  );
903
897
 
904
- // Track which knobs the user has explicitly changed in this session.
905
- const [overrides, setOverrides] = useState<Set<string>>(new Set());
898
+ // Knobs the user has set. Seeded from the knobs that differ from what the
899
+ // active preset supplies, which is what the mp.ov.* cookies hold: core's
900
+ // TanstackDevtools keys the shell on the scheme, so a Scheme click remounts
901
+ // this panel, and a reload does the same.
902
+ const [overrides, setOverrides] = useState<Set<string>>(() => {
903
+ const presetKnobs = activePresetName ? getPreset(activePresetName)?.knobs : undefined;
904
+ return new Set(
905
+ knobKeys.filter((key) => knobs[key] !== (presetKnobs?.[key] ?? defaultKnobs[key])),
906
+ );
907
+ });
906
908
  const [pinnedKnobs, setPinnedKnobs] = useState<Set<string>>(() => {
907
909
  try {
908
910
  const raw = storage.getItem(pinnedKnobsStorageKey);
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { animationNames, animations } from "../animations/css";
3
+ import { extractAnimationNames } from "./animationOptions";
4
+
5
+ describe("extractAnimationNames (MPO-447)", () => {
6
+ it("lists the registered animations of a createTamagui config, not the driver's API", () => {
7
+ const names = extractAnimationNames({ animations });
8
+ expect(names).toEqual([...animationNames]);
9
+ for (const member of [
10
+ "usePresence",
11
+ "ResetPresence",
12
+ "inputStyle",
13
+ "outputStyle",
14
+ "useAnimatedNumber",
15
+ "useAnimations",
16
+ ]) {
17
+ expect(names).not.toContain(member);
18
+ }
19
+ });
20
+
21
+ it("offers exactly the house names on the house driver", () => {
22
+ expect(extractAnimationNames({ animations })).toEqual([
23
+ "bouncy",
24
+ "lazy",
25
+ "slow",
26
+ "medium",
27
+ "quick",
28
+ "tooltip",
29
+ "snappy",
30
+ "gentle",
31
+ ]);
32
+ });
33
+
34
+ it("reads an app's own driver names when it registers different ones", () => {
35
+ const driver = { ...animations, animations: { fast: "ease-in 50ms", calm: "ease-in 900ms" } };
36
+ expect(extractAnimationNames({ animations: driver })).toEqual(["fast", "calm"]);
37
+ });
38
+
39
+ it("falls back to the house names without a config or without named animations", () => {
40
+ expect(extractAnimationNames(undefined)).toEqual([...animationNames]);
41
+ expect(extractAnimationNames({})).toEqual([...animationNames]);
42
+ expect(extractAnimationNames({ animations: { animations: {} } })).toEqual([...animationNames]);
43
+ });
44
+ });
@@ -0,0 +1,22 @@
1
+ import { animationNames } from "../animations/index";
2
+
3
+ function isRecord(value: unknown): value is Record<string, unknown> {
4
+ return typeof value === "object" && value !== null;
5
+ }
6
+
7
+ /**
8
+ * The animation names a Tamagui config registers, for the panel's Animation
9
+ * select. `createTamagui()` stores the resolved driver at `config.animations`,
10
+ * and every driver `createAnimations()` builds keeps its named animations one
11
+ * level down, on `driver.animations`. The driver's own members (`usePresence`,
12
+ * `useAnimations`, `inputStyle`, ...) are API, not animations (MPO-447).
13
+ */
14
+ export function extractAnimationNames(tamaguiConfig: unknown): string[] {
15
+ const driver = isRecord(tamaguiConfig) ? tamaguiConfig.animations : undefined;
16
+ const named = isRecord(driver) ? driver.animations : undefined;
17
+ if (isRecord(named)) {
18
+ const keys = Object.keys(named);
19
+ if (keys.length > 0) return keys;
20
+ }
21
+ return [...animationNames];
22
+ }
@@ -1,5 +1,9 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { resolveAaSolidFill, resolveAccentTintedSurface } from "./readableColor";
2
+ import {
3
+ resolveAaSolidFill,
4
+ resolveAccentTintedSurface,
5
+ resolveLegibleInkOn,
6
+ } from "./readableColor";
3
7
  import { contrastRatio, normalizeToHex, relativeLuminance } from "./colorRules";
4
8
 
5
9
  // The stock accent ramp (defaults/accent.ts) as the theme resolves it.
@@ -166,3 +170,49 @@ describe("resolveAaSolidFill", () => {
166
170
  ).toBe("$accentBackground");
167
171
  });
168
172
  });
173
+
174
+ describe("resolveLegibleInkOn (MPO-448)", () => {
175
+ it("keeps the component's ink while it clears AA on the fill", () => {
176
+ expect(
177
+ resolveLegibleInkOn({
178
+ fill: "#eeecf9",
179
+ preferred: "#66606c",
180
+ paper: lightPaper,
181
+ ink: lightInk,
182
+ }),
183
+ ).toBe("#66606c");
184
+ });
185
+
186
+ it("hands a muted title on the dark accent tint to the readable anchor (4.05:1)", () => {
187
+ expect(
188
+ resolveLegibleInkOn({
189
+ fill: darkAccent[0],
190
+ preferred: "#a09ba6",
191
+ paper: darkPaper,
192
+ ink: darkInk,
193
+ }),
194
+ ).toBe(darkInk);
195
+ });
196
+
197
+ it("falls through the anchors to a pole when neither reads (dark blue chip)", () => {
198
+ const picked = resolveLegibleInkOn({
199
+ fill: "#2870bd",
200
+ preferred: "#c2e6ff",
201
+ paper: darkPaper,
202
+ ink: darkInk,
203
+ });
204
+ expect(picked).toBe("#ffffff");
205
+ expect(contrastRatio(lum(picked!), lum("#2870bd"))).toBeGreaterThanOrEqual(4.5);
206
+ });
207
+
208
+ it("returns the preferred ink when there is no fill to read against", () => {
209
+ expect(
210
+ resolveLegibleInkOn({
211
+ fill: undefined,
212
+ preferred: "#a09ba6",
213
+ paper: darkPaper,
214
+ ink: darkInk,
215
+ }),
216
+ ).toBe("#a09ba6");
217
+ });
218
+ });
@@ -3,6 +3,7 @@ import { useTheme as useTamaguiTheme } from "tamagui";
3
3
  import {
4
4
  aaTextContrastRatio,
5
5
  contrastRatio,
6
+ holdInkFloor,
6
7
  normalizeToHex,
7
8
  pickReadableForeground,
8
9
  relativeLuminance,
@@ -22,11 +23,10 @@ import {
22
23
  * (paper `$color1` / ink `$color12`) carries more contrast on the ACTUAL
23
24
  * fill — never assumed.
24
25
  *
25
- * 2. Accent-hued text on the PAGE (`useAccentOnSurface`). `$accentColor` is the
26
- * on-fill foreground; used as a link/emphasis color on the neutral page it
27
- * reads ~1.9:1 in dark. The readable accent is the most chromatic step of
28
- * the theme's own accent ramp that still clears AA (4.5:1) on both page
29
- * surfaces — a mid accent in light, a pale accent in dark.
26
+ * 2. Accent-hued text on the PAGE (`useAccentOnSurface`). The readable accent
27
+ * is the most chromatic step of the theme's own accent ramp that still
28
+ * clears AA (4.5:1) on both page surfaces — a mid accent in light, a pale
29
+ * accent in dark.
30
30
  *
31
31
  * Both read live theme values so they stay correct across schemes, tints and
32
32
  * custom accent palettes; they mirror the runtime pattern already used by the
@@ -47,8 +47,9 @@ function resolveTokenValue(
47
47
  * / "$color8", or an already-resolved color value — e.g. the step returned by
48
48
  * `useAccentTintedSurface`): the scheme's paper (`$color1`) or ink (`$color12`)
49
49
  * anchor, whichever contrasts more on the resolved fill, returned as a concrete
50
- * value so no nested sub-theme can flip it. Returns `undefined` when any color
51
- * fails to resolve/parse — callers keep their own default in that case.
50
+ * value so no nested sub-theme can flip it. When neither anchor clears AA on
51
+ * the fill, the pole that does (`holdInkFloor`). Returns `undefined` when any
52
+ * color fails to resolve/parse — callers keep their own default in that case.
52
53
  */
53
54
  export function useReadableTextOn(fill_: string | undefined): string | undefined {
54
55
  const theme = useTamaguiTheme() as unknown as Record<string, { val?: unknown } | undefined>;
@@ -58,10 +59,61 @@ export function useReadableTextOn(fill_: string | undefined): string | undefined
58
59
  return useMemo(() => {
59
60
  if (!fill || !paper || !ink) return undefined;
60
61
  if (!normalizeToHex(fill) || !normalizeToHex(paper) || !normalizeToHex(ink)) return undefined;
61
- return pickReadableForeground(fill, paper, ink);
62
+ return holdInkFloor(fill, pickReadableForeground(fill, paper, ink));
62
63
  }, [fill, paper, ink]);
63
64
  }
64
65
 
66
+ /**
67
+ * Pure resolver behind {@link useLegibleInkOn}: `preferred` when it clears AA
68
+ * on `fill`, otherwise the readable anchor (paper or ink) held to the floor
69
+ * by `holdInkFloor`. Any unparseable input returns `preferred`.
70
+ */
71
+ export function resolveLegibleInkOn({
72
+ fill,
73
+ preferred,
74
+ paper,
75
+ ink,
76
+ }: {
77
+ fill?: string;
78
+ preferred?: string;
79
+ paper?: string;
80
+ ink?: string;
81
+ }): string | undefined {
82
+ const fillHex = fill ? normalizeToHex(fill) : null;
83
+ const preferredHex = preferred ? normalizeToHex(preferred) : null;
84
+ if (!fill || !fillHex || !preferred || !preferredHex) return preferred;
85
+ const fillLum = relativeLuminance(fillHex);
86
+ if (contrastRatio(relativeLuminance(preferredHex), fillLum) >= aaTextContrastRatio) {
87
+ return preferred;
88
+ }
89
+ const anchor =
90
+ paper && ink && normalizeToHex(paper) && normalizeToHex(ink)
91
+ ? pickReadableForeground(fill, paper, ink)
92
+ : preferred;
93
+ return holdInkFloor(fill, anchor);
94
+ }
95
+
96
+ /**
97
+ * Keep a component's own ink (`preferred`, e.g. a chip's `$blue12` or a
98
+ * muted `$color11` title) on `fill` while it clears AA, and hand over to the
99
+ * readable anchor, then a pole, where it does not. The token comes back
100
+ * untouched when it passes, so a scheme where the pair already reads keeps
101
+ * its exact paint; a replacement comes back as a concrete color.
102
+ */
103
+ export function useLegibleInkOn(fill_: string | undefined, preferred: string): string {
104
+ const theme = useTamaguiTheme() as unknown as Record<string, { val?: unknown } | undefined>;
105
+ const fill = fill_?.startsWith("$") ? resolveTokenValue(theme, fill_) : fill_;
106
+ const preferredValue = preferred.startsWith("$")
107
+ ? resolveTokenValue(theme, preferred)
108
+ : preferred;
109
+ const paper = resolveTokenValue(theme, "color1");
110
+ const ink = resolveTokenValue(theme, "color12");
111
+ return useMemo(() => {
112
+ const resolved = resolveLegibleInkOn({ fill, preferred: preferredValue, paper, ink });
113
+ return !resolved || resolved === preferredValue ? preferred : resolved;
114
+ }, [fill, preferred, preferredValue, paper, ink]);
115
+ }
116
+
65
117
  /**
66
118
  * Accent-hued text/link color readable on the page surfaces (color1/color2).
67
119
  * Scans the theme's own accent ramp for the most chromatic step that clears
@@ -195,11 +247,11 @@ function bestAnchorContrast(fill: string, paperLum: number, inkLum: number): num
195
247
  /**
196
248
  * Pick an accent solid that can carry AA text (paper or ink ≥ 4.5).
197
249
  *
198
- * `$accentBackground` in the stock dark ramp is a mid-chroma solid whose
199
- * better scheme-anchor pair measures 4.02:1 — over the graphic 3:1 tier and
200
- * under the in-repo text floor. Walking to the nearest passing solid keeps
201
- * the mark on the accent channel (LC-05) instead of dropping to ink or a
202
- * tinted surface.
250
+ * A mid-chroma solid can clear the graphic 3:1 tier and still miss the text
251
+ * floor with both scheme anchors (the stock dark `#7766cc` measured 4.02:1
252
+ * before the build held `$accentBackground` to it, MPO-448). Walking to the
253
+ * nearest passing solid keeps the mark on the accent channel (LC-05) instead
254
+ * of dropping to ink or a tinted surface.
203
255
  *
204
256
  * Returns the preferred token when it already clears the floor, otherwise
205
257
  * the solid token whose resolved luminance is closest to the preferred fill.
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { contrastRatio, normalizeToHex, relativeLuminance } from "./colorRules";
3
+ import { createThemesBuilder } from "./createThemes";
4
+ import { defaultAccentTheme } from "./defaults/accent";
5
+ import { defaultBaseTheme } from "./defaults/base";
6
+ import { defaultBuilderOptions } from "./defaults/builderOptions";
7
+
8
+ const themes = createThemesBuilder(
9
+ defaultBaseTheme,
10
+ defaultAccentTheme,
11
+ defaultBuilderOptions,
12
+ ).themes() as Record<string, Record<string, string>>;
13
+
14
+ const hex = (raw: string) => {
15
+ const value = normalizeToHex(raw);
16
+ if (!value) throw new Error(`not an opaque colour: ${raw}`);
17
+ return value;
18
+ };
19
+ const ratio = (a: string, b: string) =>
20
+ contrastRatio(relativeLuminance(hex(a)), relativeLuminance(hex(b)));
21
+
22
+ const hues = ["purple", "green", "blue", "orange", "red"] as const;
23
+ const token = (hue: (typeof hues)[number]) => `syntax${hue[0].toUpperCase()}${hue.slice(1)}`;
24
+
25
+ describe("MPO-468: code syntax ink holds Axiom 12 on the code surface", () => {
26
+ for (const scheme of ["light", "dark"] as const) {
27
+ const theme = themes[scheme];
28
+ for (const hue of hues) {
29
+ it(`${scheme} $${token(hue)} reads at 4.5:1 on $color3 and on the $${hue}3 wash`, () => {
30
+ expect(ratio(theme[token(hue)], theme.color3)).toBeGreaterThanOrEqual(4.5);
31
+ expect(ratio(theme[token(hue)], theme[`${hue}3`])).toBeGreaterThanOrEqual(4.5);
32
+ });
33
+ }
34
+ }
35
+
36
+ it("dark keeps step 11; light walks green, blue and orange toward step 12", () => {
37
+ for (const hue of hues) {
38
+ expect(themes.dark[token(hue)]).toBe(hex(themes.dark[`${hue}11`]));
39
+ }
40
+ expect(themes.light.syntaxPurple).toBe(hex(themes.light.purple11));
41
+ expect(themes.light.syntaxRed).toBe(hex(themes.light.red11));
42
+ expect(themes.light.syntaxGreen).toBe("#207c54");
43
+ expect(themes.light.syntaxBlue).toBe("#0d6dc3");
44
+ expect(themes.light.syntaxOrange).toBe("#bb4904");
45
+ });
46
+
47
+ it("the tokens ride exactly the themes that carry the hue ramps", () => {
48
+ for (const theme of Object.values(themes)) {
49
+ expect(Boolean(theme.syntaxGreen)).toBe(Boolean(theme.green11 && theme.color3));
50
+ }
51
+ });
52
+ });
@@ -103,6 +103,14 @@ const sizeTokenToKnobSize: Record<string, Knobs["size"]> = {
103
103
 
104
104
  export const solidButtonIntents = new Set(["error", "warning", "success"]);
105
105
 
106
+ /**
107
+ * Themes that are solid by palette shape: the accent slot's steps 1-10 are all
108
+ * brand surfaces (defaults/accent.ts), so the LC-40 wash ($color3/$color6)
109
+ * lands one step off the fill and never reads disabled (MPO-433: ΔE 2.5 in
110
+ * dark). A control themed by one paints the wash from its host scope.
111
+ */
112
+ export const solidPaletteThemes = new Set(["accent"]);
113
+
106
114
  /**
107
115
  * Resolve knobs → { knobProps }.
108
116
  *
@@ -51,6 +51,14 @@ export interface ResolveChartPaletteInput {
51
51
  * resolve palettes for any scheme/identity combination.
52
52
  */
53
53
  export declare function resolveChartPalette(input: ResolveChartPaletteInput): ChartPalette;
54
+ /**
55
+ * The accent solids ordered nearest-first in luminance to `identity`, so a
56
+ * chart identity that misses the 3:1 mark floor on the reference cards falls
57
+ * to the next accent step before a neutral one. `$accentBackground` is held
58
+ * to the text floor (MPO-448), which in dark lands it one step deeper than
59
+ * the marks need; the chart keeps the accent channel on the step beside it.
60
+ */
61
+ export declare function accentIdentityCandidates(identity: string | undefined, solids: readonly (string | undefined)[]): string[];
54
62
  /**
55
63
  * Canonical chart palette for the active Tamagui theme.
56
64
  *
@@ -1 +1 @@
1
- {"version":3,"file":"chartPalette.d.ts","sourceRoot":"","sources":["../../src/theme/chartPalette.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,sEAAsE;IACtE,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/D;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,kBAAkB,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;CACtD;AAkJD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,YAAY,CAyBjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAe9C"}
1
+ {"version":3,"file":"chartPalette.d.ts","sourceRoot":"","sources":["../../src/theme/chartPalette.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,sEAAsE;IACtE,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/D;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,kBAAkB,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;CACtD;AAkJD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,YAAY,CAyBjF;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GACtC,MAAM,EAAE,CAaV;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,YAAY,CA2B9C"}
@@ -90,6 +90,15 @@ export declare function resolveThemeContrast(themes: Record<string, Record<strin
90
90
  * `candidateA` unchanged when any color fails to parse (safe default).
91
91
  */
92
92
  export declare function pickReadableForeground(fill: string, candidateA: string, candidateB: string): string;
93
+ /**
94
+ * Axiom 12 backstop for ink on a fill: `ink` when it clears `floor` on
95
+ * `fill`, otherwise the pole (`#000000` / `#ffffff`) that reads better on it,
96
+ * when that pole beats `ink`. A mid-luminance fill, such as a literal
97
+ * `#cc4e00` bar or a Radix step-8 chip in dark, can sit where neither scheme
98
+ * anchor clears AA while a pole does. Returns `ink` unchanged when a color
99
+ * fails to parse.
100
+ */
101
+ export declare function holdInkFloor(fill: string, ink: string, floor?: number): string;
93
102
  /**
94
103
  * Given a 12-color scale (hex strings), a background step, and a starting
95
104
  * text step, scan towards `preferredDir` for the first step whose contrast
@@ -1 +1 @@
1
- {"version":3,"file":"colorRules.d.ts","sourceRoot":"","sources":["../../src/theme/colorRules.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"colorRules.d.ts","sourceRoot":"","sources":["../../src/theme/colorRules.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"createThemes.d.ts","sourceRoot":"","sources":["../../src/theme/createThemes.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;AAqEhC,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB,CACrC,WAAW,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,EACvC,YAAY,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE;IAExC,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B,CACzC,WAAW,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,EACvC,YAAY,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,EACxC,WAAW,SAAS,OAAO,GAAG,OAAO,EACrC,YAAY,SAAS,OAAO,GAAG,OAAO,CACtC,SAAQ,sBAAsB,CAAC,WAAW,EAAE,YAAY,CAAC;IACzD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,mBAAmB,CAAC,EAAE,iBAAiB,CAAC,qBAAqB,CAAC,CAAC;IAC/D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACzF;AA8BD,eAAO,MAAM,YAAY,aA6BvB,CAAC;AA6FH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,0BAA0B,EACrC,WAAW,EAAE,sBAAsB,EACnC,OAAO,CAAC,EAAE,0BAA0B;;aAqLzB,CAAC,SAAS,MAAM,QAAQ,CAAC,SAAS,sBAAsB,GAuB1D,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE;;+BAAkC;EAG7E"}
1
+ {"version":3,"file":"createThemes.d.ts","sourceRoot":"","sources":["../../src/theme/createThemes.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAC;AA4EhC,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB,CACrC,WAAW,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,EACvC,YAAY,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE;IAExC,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B,CACzC,WAAW,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,EACvC,YAAY,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,EACxC,WAAW,SAAS,OAAO,GAAG,OAAO,EACrC,YAAY,SAAS,OAAO,GAAG,OAAO,CACtC,SAAQ,sBAAsB,CAAC,WAAW,EAAE,YAAY,CAAC;IACzD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,mBAAmB,CAAC,EAAE,iBAAiB,CAAC,qBAAqB,CAAC,CAAC;IAC/D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACzF;AA8BD,eAAO,MAAM,YAAY,aA6BvB,CAAC;AA0OH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,0BAA0B,EACrC,WAAW,EAAE,sBAAsB,EACnC,OAAO,CAAC,EAAE,0BAA0B;;aAyLzB,CAAC,SAAS,MAAM,QAAQ,CAAC,SAAS,sBAAsB,GAuB1D,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,SAAS,CAAC,EAAE;;+BAAkC;EAG7E"}
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeDevtoolsPanel.d.ts","sourceRoot":"","sources":["../../../src/theme/devtools/ThemeDevtoolsPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAyMH,KAAK,UAAU,GAAG;IAChB,OAAO,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IACrC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;CAC/C,CAAC;AAkiBF,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,UAAU,CAAC;IAEjC;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAEjC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7C;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAmDD,wBAAgB,kBAAkB,CAAC,EACjC,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,GACd,EAAE,uBAAuB,2CAs7BzB"}
1
+ {"version":3,"file":"ThemeDevtoolsPanel.d.ts","sourceRoot":"","sources":["../../../src/theme/devtools/ThemeDevtoolsPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AA+LH,KAAK,UAAU,GAAG;IAChB,OAAO,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IACrC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;CAC/C,CAAC;AAsiBF,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,UAAU,CAAC;IAEjC;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAEjC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAExB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAEvB,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7C;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAmDD,wBAAgB,kBAAkB,CAAC,EACjC,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,GACd,EAAE,uBAAuB,2CA87BzB"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The animation names a Tamagui config registers, for the panel's Animation
3
+ * select. `createTamagui()` stores the resolved driver at `config.animations`,
4
+ * and every driver `createAnimations()` builds keeps its named animations one
5
+ * level down, on `driver.animations`. The driver's own members (`usePresence`,
6
+ * `useAnimations`, `inputStyle`, ...) are API, not animations (MPO-447).
7
+ */
8
+ export declare function extractAnimationNames(tamaguiConfig: unknown): string[];
9
+ //# sourceMappingURL=animationOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animationOptions.d.ts","sourceRoot":"","sources":["../../../src/theme/devtools/animationOptions.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,OAAO,GAAG,MAAM,EAAE,CAQtE"}
@@ -3,10 +3,30 @@
3
3
  * / "$color8", or an already-resolved color value — e.g. the step returned by
4
4
  * `useAccentTintedSurface`): the scheme's paper (`$color1`) or ink (`$color12`)
5
5
  * anchor, whichever contrasts more on the resolved fill, returned as a concrete
6
- * value so no nested sub-theme can flip it. Returns `undefined` when any color
7
- * fails to resolve/parse — callers keep their own default in that case.
6
+ * value so no nested sub-theme can flip it. When neither anchor clears AA on
7
+ * the fill, the pole that does (`holdInkFloor`). Returns `undefined` when any
8
+ * color fails to resolve/parse — callers keep their own default in that case.
8
9
  */
9
10
  export declare function useReadableTextOn(fill_: string | undefined): string | undefined;
11
+ /**
12
+ * Pure resolver behind {@link useLegibleInkOn}: `preferred` when it clears AA
13
+ * on `fill`, otherwise the readable anchor (paper or ink) held to the floor
14
+ * by `holdInkFloor`. Any unparseable input returns `preferred`.
15
+ */
16
+ export declare function resolveLegibleInkOn({ fill, preferred, paper, ink, }: {
17
+ fill?: string;
18
+ preferred?: string;
19
+ paper?: string;
20
+ ink?: string;
21
+ }): string | undefined;
22
+ /**
23
+ * Keep a component's own ink (`preferred`, e.g. a chip's `$blue12` or a
24
+ * muted `$color11` title) on `fill` while it clears AA, and hand over to the
25
+ * readable anchor, then a pole, where it does not. The token comes back
26
+ * untouched when it passes, so a scheme where the pair already reads keeps
27
+ * its exact paint; a replacement comes back as a concrete color.
28
+ */
29
+ export declare function useLegibleInkOn(fill_: string | undefined, preferred: string): string;
10
30
  /**
11
31
  * Accent-hued text/link color readable on the page surfaces (color1/color2).
12
32
  * Scans the theme's own accent ramp for the most chromatic step that clears
@@ -50,11 +70,11 @@ export interface ResolveAaSolidFillInput {
50
70
  /**
51
71
  * Pick an accent solid that can carry AA text (paper or ink ≥ 4.5).
52
72
  *
53
- * `$accentBackground` in the stock dark ramp is a mid-chroma solid whose
54
- * better scheme-anchor pair measures 4.02:1 — over the graphic 3:1 tier and
55
- * under the in-repo text floor. Walking to the nearest passing solid keeps
56
- * the mark on the accent channel (LC-05) instead of dropping to ink or a
57
- * tinted surface.
73
+ * A mid-chroma solid can clear the graphic 3:1 tier and still miss the text
74
+ * floor with both scheme anchors (the stock dark `#7766cc` measured 4.02:1
75
+ * before the build held `$accentBackground` to it, MPO-448). Walking to the
76
+ * nearest passing solid keeps the mark on the accent channel (LC-05) instead
77
+ * of dropping to ink or a tinted surface.
58
78
  *
59
79
  * Returns the preferred token when it already clears the floor, otherwise
60
80
  * the solid token whose resolved luminance is closest to the preferred fill.
@@ -1 +1 @@
1
- {"version":3,"file":"readableColor.d.ts","sourceRoot":"","sources":["../../src/theme/readableColor.ts"],"names":[],"mappings":"AA4CA;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAU/E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CA8B3C;AA0BD,MAAM,WAAW,+BAA+B;IAC9C,mEAAmE;IACnE,KAAK,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAC9B,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,0EAA0E;AAC1E,wBAAgB,0BAA0B,CAAC,EACzC,KAAK,EACL,KAAK,EACL,GAAG,GACJ,EAAE,+BAA+B,GAAG,MAAM,GAAG,SAAS,CAQtD;AAED,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAW3D;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,6DAA6D;IAC7D,SAAS,EAAE,gBAAgB,CAAC;IAC5B;;;OAGG;IACH,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,MAAM,EACN,KAAK,EACL,GAAG,EACH,KAA2B,GAC5B,EAAE,uBAAuB,GAAG,MAAM,CA0BlC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAuB7D"}
1
+ {"version":3,"file":"readableColor.d.ts","sourceRoot":"","sources":["../../src/theme/readableColor.ts"],"names":[],"mappings":"AA4CA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAU/E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,SAAS,EACT,KAAK,EACL,GAAG,GACJ,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,GAAG,SAAS,CAarB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAYpF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CA8B3C;AA0BD,MAAM,WAAW,+BAA+B;IAC9C,mEAAmE;IACnE,KAAK,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAC9B,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,0EAA0E;AAC1E,wBAAgB,0BAA0B,CAAC,EACzC,KAAK,EACL,KAAK,EACL,GAAG,GACJ,EAAE,+BAA+B,GAAG,MAAM,GAAG,SAAS,CAQtD;AAED,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAW3D;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,6DAA6D;IAC7D,SAAS,EAAE,gBAAgB,CAAC;IAC5B;;;OAGG;IACH,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,MAAM,EACN,KAAK,EACL,GAAG,EACH,KAA2B,GAC5B,EAAE,uBAAuB,GAAG,MAAM,CA0BlC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAuB7D"}
@@ -18,6 +18,13 @@ export interface UseResolvedKnobsOptions {
18
18
  compact?: boolean;
19
19
  }
20
20
  export declare const solidButtonIntents: Set<string>;
21
+ /**
22
+ * Themes that are solid by palette shape: the accent slot's steps 1-10 are all
23
+ * brand surfaces (defaults/accent.ts), so the LC-40 wash ($color3/$color6)
24
+ * lands one step off the fill and never reads disabled (MPO-433: ΔE 2.5 in
25
+ * dark). A control themed by one paints the wash from its host scope.
26
+ */
27
+ export declare const solidPaletteThemes: Set<string>;
21
28
  /**
22
29
  * Resolve knobs → { knobProps }.
23
30
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useResolvedKnobs.d.ts","sourceRoot":"","sources":["../../src/theme/useResolvedKnobs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,SAAS,CAAC;AAIxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAWlE,eAAO,MAAM,aAAa,wCAAqC,CAAC;AAEhE,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEhD;AAyDD,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,mCAAmC;IACnC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAYD,eAAO,MAAM,kBAAkB,aAA2C,CAAC;AAE3E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,aAAa,CAuGjF"}
1
+ {"version":3,"file":"useResolvedKnobs.d.ts","sourceRoot":"","sources":["../../src/theme/useResolvedKnobs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,UAAU,EAAgB,MAAM,SAAS,CAAC;AAIxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAWlE,eAAO,MAAM,aAAa,wCAAqC,CAAC;AAEhE,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEhD;AAyDD,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,mCAAmC;IACnC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAYD,eAAO,MAAM,kBAAkB,aAA2C,CAAC;AAE3E;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,aAAsB,CAAC;AAEtD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,aAAa,CAuGjF"}