@multiplatform.one/theme 7.3.0 → 7.4.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.3.0",
3
+ "version": "7.4.0",
4
4
  "description": "Tamagui theme system for multiplatform.one",
5
5
  "keywords": [
6
6
  "multiplatform",
@@ -53,8 +53,8 @@
53
53
  "@tamagui/toast": "2.7.6",
54
54
  "@tamagui/web": "2.7.6",
55
55
  "react-cookie": "^8.1.2",
56
- "@multiplatform.one/platform": "7.3.0",
57
- "@multiplatform.one/store": "7.3.0"
56
+ "@multiplatform.one/platform": "7.4.0",
57
+ "@multiplatform.one/store": "7.4.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@tamagui/animations-css": "2.7.6",
@@ -69,8 +69,8 @@
69
69
  "tamagui": "2.7.6",
70
70
  "typescript": "~5.9.3",
71
71
  "vitest": "^4.1.5",
72
- "@multiplatform.one/config": "7.3.0",
73
- "@multiplatform.one/test-utils": "7.3.0"
72
+ "@multiplatform.one/config": "7.4.0",
73
+ "@multiplatform.one/test-utils": "7.4.0"
74
74
  },
75
75
  "peerDependencies": {
76
76
  "@tamagui/animations-css": "^2.0.0-rc",
@@ -26,6 +26,7 @@ export * from "./resolveKnobs";
26
26
  export * from "./shared";
27
27
  export * from "./sizeRecipes";
28
28
  export * from "./Surface";
29
+ export * from "./tileSizes";
29
30
 
30
31
  export * from "./theme";
31
32
  export * from "./Tint";
@@ -16,6 +16,14 @@
16
16
  * `baseHeight * stepRatio ** step`.
17
17
  */
18
18
 
19
+ import {
20
+ cloneTileSizeInputs,
21
+ generateTileSizeLadder,
22
+ resolveTileSizeInputs,
23
+ type TileSizeInputs,
24
+ type TileSizeLadder,
25
+ } from "./tileSizes";
26
+
19
27
  export const SIZE_RECIPE_TOKENS = [
20
28
  "$1",
21
29
  "$1.5",
@@ -57,6 +65,11 @@ export type SizeRecipeInputs = {
57
65
  compactRatios: SizeRecipeRatios;
58
66
  radiusPx: number;
59
67
  touchFloor: number;
68
+ /**
69
+ * Tile-size ladder inputs (MPO-31). Merged over `defaultTileSizeInputs`;
70
+ * the ladder table derives from these the way every other table does.
71
+ */
72
+ tile?: Partial<TileSizeInputs>;
60
73
  };
61
74
 
62
75
  /** @deprecated Use `SizeRecipeInputs`. */
@@ -106,6 +119,8 @@ export type GeneratedSizeRecipes = {
106
119
  typeVariants: Record<string, SizeRecipeTypeVariant>;
107
120
  iconVariants: Record<string, SizeRecipeIconVariant>;
108
121
  families: RecipeFamilies;
122
+ /** Tile-size ladder (MPO-31) — derived from `inputs.tile` over the measured defaults. */
123
+ tiles: TileSizeLadder;
109
124
  };
110
125
 
111
126
  export const SIZE_TOKEN_EXPONENTS: Record<SizeRecipeToken, number> = {
@@ -205,6 +220,7 @@ export function cloneSizeRecipeInputs(inputs: SizeRecipeInputs): SizeRecipeInput
205
220
  sizeFactors: inputs.sizeFactors ? { ...inputs.sizeFactors } : undefined,
206
221
  ratios: { ...inputs.ratios },
207
222
  compactRatios: { ...inputs.compactRatios },
223
+ tile: inputs.tile ? cloneTileSizeInputs(inputs.tile) : undefined,
208
224
  };
209
225
  }
210
226
 
@@ -228,6 +244,10 @@ export function resolveSizeRecipeInputs(partial: Partial<SizeRecipeInputs> = {})
228
244
  touchFloor: partial.touchFloor ?? DEFAULT_SIZE_RECIPE_INPUTS.touchFloor,
229
245
  };
230
246
 
247
+ if (partial.tile !== undefined) {
248
+ resolved.tile = partial.tile ? resolveTileSizeInputs(partial.tile) : undefined;
249
+ }
250
+
231
251
  if (hasTokenHeights) {
232
252
  resolved.tokenHeights = partial.tokenHeights ? { ...partial.tokenHeights } : undefined;
233
253
  if (hasSizeFactors) {
@@ -383,5 +403,6 @@ export function generateSizeRecipes(partial: Partial<SizeRecipeInputs> = {}): Ge
383
403
  typeVariants: control.typeVariants,
384
404
  iconVariants: control.iconVariants,
385
405
  families,
406
+ tiles: generateTileSizeLadder(resolveTileSizeInputs(inputs.tile ?? {})),
386
407
  };
387
408
  }
@@ -111,11 +111,27 @@ export const SIZE_RECIPE_COMPACT_RATIOS = recipeFamilies.controlCompact.ratios;
111
111
 
112
112
  export const SIZE_RECIPE_FAMILY_NAMES = ["control", "controlCompact"] as const;
113
113
 
114
+ /**
115
+ * Live tile-size ladder (MPO-31) — generated from `inputs.tile` over the
116
+ * measured defaults, adopted in place on `setSizeRecipeInputs` like every
117
+ * other live table. Never handwrite rungs; restyle through inputs.
118
+ */
119
+ export const tileSizeLadder = initial.tiles;
120
+
121
+ export function getTileSizeLadder(): typeof tileSizeLadder {
122
+ return tileSizeLadder;
123
+ }
124
+
114
125
  function adoptGenerated(next: GeneratedSizeRecipes): GeneratedSizeRecipes {
115
126
  adoptFamily(recipeFamilies.control, next.families.control);
116
127
  adoptFamily(recipeFamilies.controlCompact, next.families.controlCompact);
128
+ replaceRecord(
129
+ tileSizeLadder as unknown as Record<string, unknown>,
130
+ next.tiles as unknown as Record<string, unknown>,
131
+ );
117
132
  current = {
118
133
  ...next,
134
+ tiles: tileSizeLadder,
119
135
  heights: SIZE_RECIPE_HEIGHTS,
120
136
  heightsTouch: SIZE_RECIPE_HEIGHTS_TOUCH,
121
137
  recipes: sizeRecipes,
@@ -0,0 +1,139 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { generateSizeRecipes } from "./recipeInputs";
3
+ import { getSizeRecipeInputs, setSizeRecipeInputs, tileSizeLadder } from "./sizeRecipes";
4
+ import {
5
+ defaultTileSizeInputs,
6
+ generateTileSizeLadder,
7
+ mediaAspectRatios,
8
+ resolveMediaAspect,
9
+ resolveTileSizeInputs,
10
+ tileColumnsForWidth,
11
+ tileRungFlexProps,
12
+ tileSizeRungs,
13
+ } from "./tileSizes";
14
+
15
+ afterEach(() => {
16
+ setSizeRecipeInputs();
17
+ });
18
+
19
+ describe("tile-size ladder (MPO-31 H1)", () => {
20
+ it("defaults to the measured consumer ladders, not invented numbers", () => {
21
+ const ladder = generateTileSizeLadder(resolveTileSizeInputs());
22
+ // sm..xl are Lookout's TILE_SIZES basis/max (identical to the SPA's
23
+ // TILE_MIN); xs is the launcher icon-plate minmax(104px,1fr) track.
24
+ expect(ladder.sm).toEqual({ minWidth: 220, maxWidth: 300 });
25
+ expect(ladder.md).toEqual({ minWidth: 320, maxWidth: 440 });
26
+ expect(ladder.lg).toEqual({ minWidth: 480, maxWidth: 720 });
27
+ expect(ladder.xl).toEqual({ minWidth: 720, maxWidth: 1400 });
28
+ expect(ladder.xs.minWidth).toBe(104);
29
+ // xs has no measured max; it derives from maxRatio (440/320).
30
+ expect(ladder.xs.maxWidth).toBe(Math.round(104 * defaultTileSizeInputs.maxRatio));
31
+ });
32
+
33
+ it("is a generated table: rungs are monotonic and max >= min on every rung", () => {
34
+ const ladder = generateTileSizeLadder(resolveTileSizeInputs());
35
+ for (let i = 0; i < tileSizeRungs.length; i++) {
36
+ const stop = ladder[tileSizeRungs[i]];
37
+ expect(stop.maxWidth).toBeGreaterThanOrEqual(stop.minWidth);
38
+ if (i > 0) {
39
+ expect(stop.minWidth).toBeGreaterThan(ladder[tileSizeRungs[i - 1]].minWidth);
40
+ }
41
+ }
42
+ });
43
+
44
+ it("a bare baseWidth override regenerates the whole ladder geometrically", () => {
45
+ const inputs = resolveTileSizeInputs({ baseWidth: 280 });
46
+ // Scaling override drops the measured pins (the resolveSizeRecipeInputs contract).
47
+ expect(inputs.rungWidths).toBeUndefined();
48
+ expect(inputs.widthFactors).toBeUndefined();
49
+ expect(inputs.rungMaxWidths).toBeUndefined();
50
+ const ladder = generateTileSizeLadder(inputs);
51
+ expect(ladder.md.minWidth).toBe(280);
52
+ expect(ladder.lg.minWidth).toBe(Math.round(280 * 1.5));
53
+ expect(ladder.xl.minWidth).toBe(Math.round(280 * 1.5 ** 2));
54
+ expect(ladder.sm.minWidth).toBe(Math.round(280 / 1.5));
55
+ expect(ladder.xs.minWidth).toBe(Math.round(280 / 1.5 ** 2));
56
+ for (const rung of tileSizeRungs) {
57
+ expect(ladder[rung].maxWidth).toBe(
58
+ Math.round(ladder[rung].minWidth * defaultTileSizeInputs.maxRatio),
59
+ );
60
+ }
61
+ });
62
+
63
+ it("explicit rungMaxWidths pin maxes; maxRatio covers the rest", () => {
64
+ const ladder = generateTileSizeLadder(
65
+ resolveTileSizeInputs({ baseWidth: 300, maxRatio: 2, rungMaxWidths: { md: 555 } }),
66
+ );
67
+ expect(ladder.md).toEqual({ minWidth: 300, maxWidth: 555 });
68
+ expect(ladder.lg.maxWidth).toBe(ladder.lg.minWidth * 2);
69
+ });
70
+
71
+ it("tileRungFlexProps is the wrapping-flex tile fragment (the Lookout idiom)", () => {
72
+ const ladder = generateTileSizeLadder(resolveTileSizeInputs());
73
+ expect(tileRungFlexProps(ladder.md)).toEqual({
74
+ flexGrow: 1,
75
+ flexBasis: 320,
76
+ minWidth: 320,
77
+ maxWidth: 440,
78
+ });
79
+ });
80
+
81
+ it("tileColumnsForWidth derives fixed-column counts from the rung and the gutter", () => {
82
+ const ladder = generateTileSizeLadder(resolveTileSizeInputs());
83
+ // A 1024 window at md with a 12px gutter fits 3 columns.
84
+ expect(tileColumnsForWidth(1024, ladder.md, 12)).toBe(3);
85
+ // xs at the launcher's own 8px gutter reproduces the springboard split:
86
+ // 6 columns at the 700px wide-breakpoint, 4 in the phone band.
87
+ expect(tileColumnsForWidth(700, ladder.xs, 8)).toBe(6);
88
+ expect(tileColumnsForWidth(500, ladder.xs, 8)).toBe(4);
89
+ // Never fewer than one column.
90
+ expect(tileColumnsForWidth(80, ladder.md)).toBe(1);
91
+ expect(tileColumnsForWidth(0, ladder.md)).toBe(1);
92
+ expect(tileColumnsForWidth(Number.NaN, ladder.md)).toBe(1);
93
+ });
94
+
95
+ it("rides recipeInputs: generateSizeRecipes carries tiles and setSizeRecipeInputs rewrites the live ladder in place", () => {
96
+ expect(generateSizeRecipes().tiles.md).toEqual({ minWidth: 320, maxWidth: 440 });
97
+
98
+ const live = tileSizeLadder;
99
+ expect(live.md.minWidth).toBe(320);
100
+ setSizeRecipeInputs({ tile: { baseWidth: 280 } });
101
+ // Same object identity (styled()-spread safety), new generated rows.
102
+ expect(tileSizeLadder).toBe(live);
103
+ expect(tileSizeLadder.md.minWidth).toBe(280);
104
+ expect(tileSizeLadder.lg.minWidth).toBe(420);
105
+ // The stored inputs round-trip through getSizeRecipeInputs.
106
+ expect(getSizeRecipeInputs().tile?.baseWidth).toBe(280);
107
+
108
+ setSizeRecipeInputs();
109
+ expect(tileSizeLadder.md).toEqual({ minWidth: 320, maxWidth: 440 });
110
+ expect(tileSizeLadder.sm).toEqual({ minWidth: 220, maxWidth: 300 });
111
+ });
112
+ });
113
+
114
+ describe("media aspect vocabulary (MPO-31 H2)", () => {
115
+ it("names resolve to their measured ratios", () => {
116
+ expect(resolveMediaAspect("square")).toBe(1);
117
+ expect(resolveMediaAspect("photo")).toBeCloseTo(4 / 3);
118
+ // The Lookout frame shape — desktop screen captures at 16:10.
119
+ expect(resolveMediaAspect("screen")).toBeCloseTo(1.6);
120
+ expect(resolveMediaAspect("video")).toBeCloseTo(16 / 9);
121
+ expect(resolveMediaAspect("portrait")).toBeCloseTo(3 / 4);
122
+ expect(Object.keys(mediaAspectRatios)).toEqual([
123
+ "square",
124
+ "photo",
125
+ "screen",
126
+ "video",
127
+ "portrait",
128
+ ]);
129
+ });
130
+
131
+ it("numbers pass through; nullish and junk fall back to square", () => {
132
+ expect(resolveMediaAspect(1.6)).toBe(1.6);
133
+ expect(resolveMediaAspect()).toBe(1);
134
+ expect(resolveMediaAspect(null)).toBe(1);
135
+ expect(resolveMediaAspect(0)).toBe(1);
136
+ expect(resolveMediaAspect(-2)).toBe(1);
137
+ expect(resolveMediaAspect(Number.POSITIVE_INFINITY)).toBe(1);
138
+ });
139
+ });
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Tile-size ladder + media-aspect vocabulary (MPO-31, design-system/12 D.3
3
+ * holes H1/H2).
4
+ *
5
+ * Every tile wall needs the same two decisions: HOW BIG is a tile (a small
6
+ * named ladder a person can pick from — the user-facing S/M/L/XL control)
7
+ * and WHAT SHAPE is its media (so the loading skeleton is the same shape as
8
+ * the image that replaces it). Before this module both were app-invented:
9
+ * the console's `launcherGeometry.ts` (03-our-surfaces.md DL-9) and
10
+ * Lookout's private `TILE_SIZES` were the same gap twice.
11
+ *
12
+ * Like the control-size recipes (LC-70 RECIPE-INPUTS), STORED INPUTS are the
13
+ * source of truth and the ladder table is DERIVED — never handwritten at a
14
+ * call site. The default rung values are the MEASURED ladders from the two
15
+ * real consumers, not invented numbers:
16
+ *
17
+ * | rung | minWidth | maxWidth | measured from |
18
+ * |------|----------|----------|------------------------------------------------|
19
+ * | xs | 104 | 143 | launcher icon-plate track `minmax(104px,1fr)` |
20
+ * | sm | 220 | 300 | Lookout `TILE_SIZES.s` / SPA `TILE_MIN.s` |
21
+ * | md | 320 | 440 | Lookout `TILE_SIZES.m` / SPA `TILE_MIN.m` |
22
+ * | lg | 480 | 720 | Lookout `TILE_SIZES.l` / SPA `TILE_MIN.l` |
23
+ * | xl | 720 | 1400 | Lookout `TILE_SIZES.xl` / SPA `TILE_MIN.xl` |
24
+ *
25
+ * `{ baseWidth: 280 }` (no rungWidths / widthFactors) regenerates the whole
26
+ * ladder geometrically from `baseWidth * stepRatio ** step`, the same
27
+ * override contract `resolveSizeRecipeInputs` gives `baseHeight`.
28
+ *
29
+ * OWNERSHIP (12-one-design.md Part B): the RUNGS are universal — generated
30
+ * here, restyled only through recipe inputs
31
+ * (`setSizeRecipeInputs({ tile })` / `createDefaultThemeConfig({ recipeInputs:
32
+ * { tile } })`). WHICH rung a surface shows is per-app (a prop, often
33
+ * persisted per user). Tile gutters stay on the space knob and tile rounding
34
+ * on the borderRadius knob — this module never emits a gap or a radius.
35
+ *
36
+ * DENSITY (LC-76): density moves SPACE only. A compact density steps the
37
+ * gutters between tiles (via the space knob) and must never re-rung the
38
+ * ladder — tile magnitude is content scale, not chrome scale, so nothing
39
+ * here reads the density knob.
40
+ */
41
+
42
+ // ── Rungs ─────────────────────────────────────────────────────
43
+
44
+ export const tileSizeRungs = ["xs", "sm", "md", "lg", "xl"] as const;
45
+
46
+ export type TileSizeRung = (typeof tileSizeRungs)[number];
47
+
48
+ /** One rung: the narrowest a tile may render, and the widest. */
49
+ export type TileSizeStop = {
50
+ minWidth: number;
51
+ maxWidth: number;
52
+ };
53
+
54
+ export type TileSizeLadder = Record<TileSizeRung, TileSizeStop>;
55
+
56
+ // ── Inputs ────────────────────────────────────────────────────
57
+
58
+ export type TileSizeInputs = {
59
+ /** Desktop px min-width of the `md` rung. */
60
+ baseWidth: number;
61
+ /** Rung-to-rung ratio when regenerating geometrically. */
62
+ stepRatio: number;
63
+ /** Exponent of each rung relative to `md` (0). */
64
+ rungSteps: Record<TileSizeRung, number>;
65
+ /**
66
+ * Absolute desktop px min-widths. When omitted, min-widths come from
67
+ * `baseWidth * stepRatio ** step` (or `widthFactors`).
68
+ */
69
+ rungWidths?: Partial<Record<TileSizeRung, number>>;
70
+ /**
71
+ * Optional per-rung scale relative to `baseWidth`. Used only when
72
+ * `rungWidths` is omitted. Factors are `stepRatio ** exponent` otherwise.
73
+ */
74
+ widthFactors?: Partial<Record<TileSizeRung, number>>;
75
+ /** maxWidth = minWidth * maxRatio for any rung `rungMaxWidths` does not pin. */
76
+ maxRatio: number;
77
+ /** Absolute desktop px max-widths (measured pins win over `maxRatio`). */
78
+ rungMaxWidths?: Partial<Record<TileSizeRung, number>>;
79
+ };
80
+
81
+ export const tileSizeStepExponents: Record<TileSizeRung, number> = {
82
+ xs: -2,
83
+ sm: -1,
84
+ md: 0,
85
+ lg: 1,
86
+ xl: 2,
87
+ };
88
+
89
+ // Measured min-widths: xs is the launcher/springboard icon-plate grid track
90
+ // (`minmax(104px,1fr)` — launcherGeometry.ts / traefik menu.go); sm..xl are
91
+ // Lookout's `TILE_SIZES` basis values, identical in the production SPA's
92
+ // `TILE_MIN` map. Kept explicit so adopting the ladder is a rendered no-op
93
+ // for both consumers.
94
+ const measuredRungWidths: Record<TileSizeRung, number> = {
95
+ xs: 104,
96
+ sm: 220,
97
+ md: 320,
98
+ lg: 480,
99
+ xl: 720,
100
+ };
101
+
102
+ /** Lossless relative table: the measured 104/220/320/480/720 ramp at baseWidth 320. */
103
+ export const defaultWidthFactors: Record<TileSizeRung, number> = {
104
+ xs: 104 / 320,
105
+ sm: 220 / 320,
106
+ md: 1,
107
+ lg: 480 / 320,
108
+ xl: 720 / 320,
109
+ };
110
+
111
+ // Measured max-widths (Lookout `TILE_SIZES` max). xl's 1.94x is the
112
+ // deliberate "one huge tile" rung; xs has no measured max (the launcher caps
113
+ // tiles by column count), so it derives from `maxRatio`.
114
+ const measuredRungMaxWidths: Partial<Record<TileSizeRung, number>> = {
115
+ sm: 300,
116
+ md: 440,
117
+ lg: 720,
118
+ xl: 1400,
119
+ };
120
+
121
+ export const defaultTileSizeInputs: TileSizeInputs = {
122
+ baseWidth: 320,
123
+ stepRatio: 1.5,
124
+ rungSteps: { ...tileSizeStepExponents },
125
+ rungWidths: { ...measuredRungWidths },
126
+ widthFactors: { ...defaultWidthFactors },
127
+ // Lookout md: 440/320. The measured sm (1.36) and lg (1.5) bracket it.
128
+ maxRatio: 440 / 320,
129
+ rungMaxWidths: { ...measuredRungMaxWidths },
130
+ };
131
+
132
+ /**
133
+ * Deep copy that PRESERVES partiality: keys the caller never set stay
134
+ * unset, so a later `resolveTileSizeInputs` still sees them as defaults
135
+ * rather than explicit undefineds.
136
+ */
137
+ export function cloneTileSizeInputs<T extends Partial<TileSizeInputs>>(inputs: T): T {
138
+ const clone = { ...inputs };
139
+ if (inputs.rungSteps) clone.rungSteps = { ...inputs.rungSteps };
140
+ if (inputs.rungWidths) clone.rungWidths = { ...inputs.rungWidths };
141
+ if (inputs.widthFactors) clone.widthFactors = { ...inputs.widthFactors };
142
+ if (inputs.rungMaxWidths) clone.rungMaxWidths = { ...inputs.rungMaxWidths };
143
+ return clone;
144
+ }
145
+
146
+ /**
147
+ * Fill defaults. `{ baseWidth: 280 }` (any scaling override without explicit
148
+ * tables) drops the measured rungWidths/widthFactors/rungMaxWidths so every
149
+ * rung regenerates from `stepRatio` — the `resolveSizeRecipeInputs` contract.
150
+ */
151
+ export function resolveTileSizeInputs(partial: Partial<TileSizeInputs> = {}): TileSizeInputs {
152
+ const scalingOverride =
153
+ partial.baseWidth != null || partial.stepRatio != null || partial.rungSteps != null;
154
+ const hasRungWidths = Object.prototype.hasOwnProperty.call(partial, "rungWidths");
155
+ const hasWidthFactors = Object.prototype.hasOwnProperty.call(partial, "widthFactors");
156
+ const hasRungMaxWidths = Object.prototype.hasOwnProperty.call(partial, "rungMaxWidths");
157
+
158
+ const resolved: TileSizeInputs = {
159
+ baseWidth: partial.baseWidth ?? defaultTileSizeInputs.baseWidth,
160
+ stepRatio: partial.stepRatio ?? defaultTileSizeInputs.stepRatio,
161
+ rungSteps: { ...defaultTileSizeInputs.rungSteps, ...partial.rungSteps },
162
+ maxRatio: partial.maxRatio ?? defaultTileSizeInputs.maxRatio,
163
+ };
164
+
165
+ if (hasRungWidths) {
166
+ resolved.rungWidths = partial.rungWidths ? { ...partial.rungWidths } : undefined;
167
+ if (hasWidthFactors) {
168
+ resolved.widthFactors = partial.widthFactors ? { ...partial.widthFactors } : undefined;
169
+ }
170
+ } else if (hasWidthFactors) {
171
+ resolved.widthFactors = partial.widthFactors ? { ...partial.widthFactors } : undefined;
172
+ } else if (!scalingOverride) {
173
+ resolved.rungWidths = { ...defaultTileSizeInputs.rungWidths };
174
+ resolved.widthFactors = { ...defaultTileSizeInputs.widthFactors };
175
+ }
176
+
177
+ if (hasRungMaxWidths) {
178
+ resolved.rungMaxWidths = partial.rungMaxWidths ? { ...partial.rungMaxWidths } : undefined;
179
+ } else if (!scalingOverride && !hasRungWidths && !hasWidthFactors) {
180
+ resolved.rungMaxWidths = { ...defaultTileSizeInputs.rungMaxWidths };
181
+ }
182
+
183
+ return resolved;
184
+ }
185
+
186
+ // ── Generation ────────────────────────────────────────────────
187
+
188
+ function clampWidth(value: number): number {
189
+ return Math.max(1, Math.round(value));
190
+ }
191
+
192
+ function minWidthForRung(rung: TileSizeRung, inputs: TileSizeInputs): number {
193
+ const explicitPx = inputs.rungWidths?.[rung];
194
+ if (explicitPx != null) return clampWidth(explicitPx);
195
+ const factor = inputs.widthFactors?.[rung];
196
+ if (factor != null) return clampWidth(inputs.baseWidth * factor);
197
+ const step = inputs.rungSteps[rung] ?? 0;
198
+ return clampWidth(inputs.baseWidth * inputs.stepRatio ** step);
199
+ }
200
+
201
+ /** Derive the full ladder from resolved inputs. Tables are generated, never handwritten. */
202
+ export function generateTileSizeLadder(inputs: TileSizeInputs): TileSizeLadder {
203
+ const ladder = {} as TileSizeLadder;
204
+ for (const rung of tileSizeRungs) {
205
+ const minWidth = minWidthForRung(rung, inputs);
206
+ const pinnedMax = inputs.rungMaxWidths?.[rung];
207
+ const maxWidth = Math.max(
208
+ minWidth,
209
+ pinnedMax != null ? clampWidth(pinnedMax) : clampWidth(minWidth * inputs.maxRatio),
210
+ );
211
+ ladder[rung] = { minWidth, maxWidth };
212
+ }
213
+ return ladder;
214
+ }
215
+
216
+ // ── Consumption helpers ───────────────────────────────────────
217
+
218
+ export type TileRungFlexProps = {
219
+ flexGrow: number;
220
+ flexBasis: number;
221
+ minWidth: number;
222
+ maxWidth: number;
223
+ };
224
+
225
+ /**
226
+ * Style fragment for a tile in a wrapping flex wall (the Lookout idiom:
227
+ * tiles grow from `minWidth` and stop at `maxWidth`). Spread it on the tile
228
+ * surface; gutters stay on the container's space-knob gap.
229
+ */
230
+ export function tileRungFlexProps(stop: TileSizeStop): TileRungFlexProps {
231
+ return {
232
+ flexGrow: 1,
233
+ flexBasis: stop.minWidth,
234
+ minWidth: stop.minWidth,
235
+ maxWidth: stop.maxWidth,
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Column count for a fixed-column tile grid (the ImageGrid / springboard
241
+ * idiom): as many columns as fit at the rung's minWidth. `gutter` is the
242
+ * inter-column gap (the space knob's value), never baked into the rung.
243
+ *
244
+ * The count is min-driven on purpose: adding a column to honor `maxWidth`
245
+ * would, by construction of the fit, drop every cell below `minWidth`
246
+ * (`(fit+1)` cells at minWidth already overflow the width). `maxWidth`
247
+ * binds in the wrapping-flex idiom (`tileRungFlexProps`), where a tile can
248
+ * stop growing without forcing a new column.
249
+ *
250
+ * At the springboard's own numbers this reproduces the launcher's split:
251
+ * `xs` (104) with the 8px launcher gutter yields 6 columns at the 700px
252
+ * wide-breakpoint and 4 in the phone band.
253
+ */
254
+ export function tileColumnsForWidth(width: number, stop: TileSizeStop, gutter = 0): number {
255
+ if (!Number.isFinite(width) || width <= 0) return 1;
256
+ return Math.max(1, Math.floor((width + gutter) / (stop.minWidth + gutter)));
257
+ }
258
+
259
+ // ── Media aspect (H2) ─────────────────────────────────────────
260
+
261
+ /**
262
+ * Named media aspect ratios (width / height). A skeleton twin drawn at the
263
+ * wrong aspect produces the exact layout jump it exists to prevent
264
+ * (ImageGrid hardcoded 1; Lookout frames are 16:10).
265
+ */
266
+ export const mediaAspectRatios = {
267
+ /** Album art, avatars, icon plates. */
268
+ square: 1,
269
+ /** Stills cameras, 4:3. */
270
+ photo: 4 / 3,
271
+ /** Desktop screen captures, 16:10 — the Lookout frame shape. */
272
+ screen: 16 / 10,
273
+ /** Video stills, 16:9. */
274
+ video: 16 / 9,
275
+ /** Portrait media, 3:4. */
276
+ portrait: 3 / 4,
277
+ } as const;
278
+
279
+ export type MediaAspectName = keyof typeof mediaAspectRatios;
280
+
281
+ /** A named aspect or an explicit width/height number. */
282
+ export type MediaAspect = MediaAspectName | number;
283
+
284
+ /** Resolve a name or number to a positive width/height ratio (default `square`). */
285
+ export function resolveMediaAspect(aspect?: MediaAspect | null): number {
286
+ if (typeof aspect === "number") {
287
+ return Number.isFinite(aspect) && aspect > 0 ? aspect : mediaAspectRatios.square;
288
+ }
289
+ if (aspect != null && aspect in mediaAspectRatios) return mediaAspectRatios[aspect];
290
+ return mediaAspectRatios.square;
291
+ }
@@ -26,6 +26,7 @@ export * from "./resolveKnobs";
26
26
  export * from "./shared";
27
27
  export * from "./sizeRecipes";
28
28
  export * from "./Surface";
29
+ export * from "./tileSizes";
29
30
  export * from "./theme";
30
31
  export * from "./Tint";
31
32
  export * from "./useResolvedKnobs";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,MAAM,IAAI,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACjF,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAE1B,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,MAAM,IAAI,gBAAgB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACjF,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAE5B,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC"}
@@ -15,6 +15,7 @@
15
15
  * tokenHeights / sizeFactors) regenerates geometrically from
16
16
  * `baseHeight * stepRatio ** step`.
17
17
  */
18
+ import { type TileSizeInputs, type TileSizeLadder } from "./tileSizes";
18
19
  export declare const SIZE_RECIPE_TOKENS: readonly ["$1", "$1.5", "$2", "$2.5", "$3", "$3.5", "$4", "$true", "$5", "$6"];
19
20
  export type SizeRecipeToken = (typeof SIZE_RECIPE_TOKENS)[number];
20
21
  export type SizeRecipeFamilyName = "control" | "controlCompact";
@@ -41,6 +42,11 @@ export type SizeRecipeInputs = {
41
42
  compactRatios: SizeRecipeRatios;
42
43
  radiusPx: number;
43
44
  touchFloor: number;
45
+ /**
46
+ * Tile-size ladder inputs (MPO-31). Merged over `defaultTileSizeInputs`;
47
+ * the ladder table derives from these the way every other table does.
48
+ */
49
+ tile?: Partial<TileSizeInputs>;
44
50
  };
45
51
  /** @deprecated Use `SizeRecipeInputs`. */
46
52
  export type RecipeInputs = SizeRecipeInputs;
@@ -91,6 +97,8 @@ export type GeneratedSizeRecipes = {
91
97
  typeVariants: Record<string, SizeRecipeTypeVariant>;
92
98
  iconVariants: Record<string, SizeRecipeIconVariant>;
93
99
  families: RecipeFamilies;
100
+ /** Tile-size ladder (MPO-31) — derived from `inputs.tile` over the measured defaults. */
101
+ tiles: TileSizeLadder;
94
102
  };
95
103
  export declare const SIZE_TOKEN_EXPONENTS: Record<SizeRecipeToken, number>;
96
104
  /** Lossless relative table: the stock Tamagui 20/24/28/32/36/40/44/52/64 ramp at baseHeight 44. */
@@ -1 +1 @@
1
- {"version":3,"file":"recipeInputs.d.ts","sourceRoot":"","sources":["../../src/theme/recipeInputs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"recipeInputs.d.ts","sourceRoot":"","sources":["../../src/theme/recipeInputs.ts"],"names":[],"mappings":"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"}
@@ -44,6 +44,13 @@ export declare const SIZE_RECIPE_HEIGHTS_TOUCH: Record<string, number>;
44
44
  export declare const SIZE_RECIPE_RATIOS: import("./recipeInputs").SizeRecipeRatios;
45
45
  export declare const SIZE_RECIPE_COMPACT_RATIOS: import("./recipeInputs").SizeRecipeRatios;
46
46
  export declare const SIZE_RECIPE_FAMILY_NAMES: readonly ["control", "controlCompact"];
47
+ /**
48
+ * Live tile-size ladder (MPO-31) — generated from `inputs.tile` over the
49
+ * measured defaults, adopted in place on `setSizeRecipeInputs` like every
50
+ * other live table. Never handwrite rungs; restyle through inputs.
51
+ */
52
+ export declare const tileSizeLadder: import("./tileSizes").TileSizeLadder;
53
+ export declare function getTileSizeLadder(): typeof tileSizeLadder;
47
54
  /** Merge `partial` over defaultRecipeInputs (not the previous override) and rewrite live tables. */
48
55
  export declare function setSizeRecipeInputs(partial?: Partial<RecipeInputs>): GeneratedSizeRecipes;
49
56
  export declare function getSizeRecipeInputs(): RecipeInputs;
@@ -1 +1 @@
1
- {"version":3,"file":"sizeRecipes.d.ts","sourceRoot":"","sources":["../../src/theme/sizeRecipes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,gBAAgB,EAEtB,MAAM,gBAAgB,CAAC;AAyBxB;;;GAGG;AACH,eAAO,MAAM,cAAc,yCAAmB,CAAC;AAE/C,oFAAoF;AACpF,eAAO,MAAM,WAAW,4BAAiC,CAAC;AAC1D,eAAO,MAAM,qBAAqB,+DAAqC,CAAC;AACxE,eAAO,MAAM,sBAAsB,gEAAsC,CAAC;AAC1E,eAAO,MAAM,sBAAsB,gEAAsC,CAAC;AAC1E,eAAO,MAAM,mBAAmB,wBAAiC,CAAC;AAClE,eAAO,MAAM,yBAAyB,wBAAsC,CAAC;AAC7E,eAAO,MAAM,kBAAkB,2CAAgC,CAAC;AAChE,eAAO,MAAM,0BAA0B,2CAAuC,CAAC;AAE/E,eAAO,MAAM,wBAAwB,wCAAyC,CAAC;AAmB/E,oGAAoG;AACpG,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,oBAAoB,CAE7F;AAED,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,uBAAuB,IAAI,oBAAoB,CAE9D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,gBAA4B,GAAG,gBAAgB,CAExF;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,YAAY,CAAC,QAAQ,CAAyB,GACrD,UAAU,CAEZ;AAOD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,gBAAgB,GAAG,gBAAgB,GAC3C,UAAU,CAQZ;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,gBAAgB,CAAA;CAAE,GAC1E,UAAU,CAKZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAE/C"}
1
+ {"version":3,"file":"sizeRecipes.d.ts","sourceRoot":"","sources":["../../src/theme/sizeRecipes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,gBAAgB,EAEtB,MAAM,gBAAgB,CAAC;AAyBxB;;;GAGG;AACH,eAAO,MAAM,cAAc,yCAAmB,CAAC;AAE/C,oFAAoF;AACpF,eAAO,MAAM,WAAW,4BAAiC,CAAC;AAC1D,eAAO,MAAM,qBAAqB,+DAAqC,CAAC;AACxE,eAAO,MAAM,sBAAsB,gEAAsC,CAAC;AAC1E,eAAO,MAAM,sBAAsB,gEAAsC,CAAC;AAC1E,eAAO,MAAM,mBAAmB,wBAAiC,CAAC;AAClE,eAAO,MAAM,yBAAyB,wBAAsC,CAAC;AAC7E,eAAO,MAAM,kBAAkB,2CAAgC,CAAC;AAChE,eAAO,MAAM,0BAA0B,2CAAuC,CAAC;AAE/E,eAAO,MAAM,wBAAwB,wCAAyC,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,cAAc,sCAAgB,CAAC;AAE5C,wBAAgB,iBAAiB,IAAI,OAAO,cAAc,CAEzD;AAwBD,oGAAoG;AACpG,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,oBAAoB,CAE7F;AAED,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,uBAAuB,IAAI,oBAAoB,CAE9D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,gBAA4B,GAAG,gBAAgB,CAExF;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,YAAY,CAAC,QAAQ,CAAyB,GACrD,UAAU,CAEZ;AAOD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,gBAAgB,GAAG,gBAAgB,GAC3C,UAAU,CAQZ;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,gBAAgB,CAAA;CAAE,GAC1E,UAAU,CAKZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAE/C"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Tile-size ladder + media-aspect vocabulary (MPO-31, design-system/12 D.3
3
+ * holes H1/H2).
4
+ *
5
+ * Every tile wall needs the same two decisions: HOW BIG is a tile (a small
6
+ * named ladder a person can pick from — the user-facing S/M/L/XL control)
7
+ * and WHAT SHAPE is its media (so the loading skeleton is the same shape as
8
+ * the image that replaces it). Before this module both were app-invented:
9
+ * the console's `launcherGeometry.ts` (03-our-surfaces.md DL-9) and
10
+ * Lookout's private `TILE_SIZES` were the same gap twice.
11
+ *
12
+ * Like the control-size recipes (LC-70 RECIPE-INPUTS), STORED INPUTS are the
13
+ * source of truth and the ladder table is DERIVED — never handwritten at a
14
+ * call site. The default rung values are the MEASURED ladders from the two
15
+ * real consumers, not invented numbers:
16
+ *
17
+ * | rung | minWidth | maxWidth | measured from |
18
+ * |------|----------|----------|------------------------------------------------|
19
+ * | xs | 104 | 143 | launcher icon-plate track `minmax(104px,1fr)` |
20
+ * | sm | 220 | 300 | Lookout `TILE_SIZES.s` / SPA `TILE_MIN.s` |
21
+ * | md | 320 | 440 | Lookout `TILE_SIZES.m` / SPA `TILE_MIN.m` |
22
+ * | lg | 480 | 720 | Lookout `TILE_SIZES.l` / SPA `TILE_MIN.l` |
23
+ * | xl | 720 | 1400 | Lookout `TILE_SIZES.xl` / SPA `TILE_MIN.xl` |
24
+ *
25
+ * `{ baseWidth: 280 }` (no rungWidths / widthFactors) regenerates the whole
26
+ * ladder geometrically from `baseWidth * stepRatio ** step`, the same
27
+ * override contract `resolveSizeRecipeInputs` gives `baseHeight`.
28
+ *
29
+ * OWNERSHIP (12-one-design.md Part B): the RUNGS are universal — generated
30
+ * here, restyled only through recipe inputs
31
+ * (`setSizeRecipeInputs({ tile })` / `createDefaultThemeConfig({ recipeInputs:
32
+ * { tile } })`). WHICH rung a surface shows is per-app (a prop, often
33
+ * persisted per user). Tile gutters stay on the space knob and tile rounding
34
+ * on the borderRadius knob — this module never emits a gap or a radius.
35
+ *
36
+ * DENSITY (LC-76): density moves SPACE only. A compact density steps the
37
+ * gutters between tiles (via the space knob) and must never re-rung the
38
+ * ladder — tile magnitude is content scale, not chrome scale, so nothing
39
+ * here reads the density knob.
40
+ */
41
+ export declare const tileSizeRungs: readonly ["xs", "sm", "md", "lg", "xl"];
42
+ export type TileSizeRung = (typeof tileSizeRungs)[number];
43
+ /** One rung: the narrowest a tile may render, and the widest. */
44
+ export type TileSizeStop = {
45
+ minWidth: number;
46
+ maxWidth: number;
47
+ };
48
+ export type TileSizeLadder = Record<TileSizeRung, TileSizeStop>;
49
+ export type TileSizeInputs = {
50
+ /** Desktop px min-width of the `md` rung. */
51
+ baseWidth: number;
52
+ /** Rung-to-rung ratio when regenerating geometrically. */
53
+ stepRatio: number;
54
+ /** Exponent of each rung relative to `md` (0). */
55
+ rungSteps: Record<TileSizeRung, number>;
56
+ /**
57
+ * Absolute desktop px min-widths. When omitted, min-widths come from
58
+ * `baseWidth * stepRatio ** step` (or `widthFactors`).
59
+ */
60
+ rungWidths?: Partial<Record<TileSizeRung, number>>;
61
+ /**
62
+ * Optional per-rung scale relative to `baseWidth`. Used only when
63
+ * `rungWidths` is omitted. Factors are `stepRatio ** exponent` otherwise.
64
+ */
65
+ widthFactors?: Partial<Record<TileSizeRung, number>>;
66
+ /** maxWidth = minWidth * maxRatio for any rung `rungMaxWidths` does not pin. */
67
+ maxRatio: number;
68
+ /** Absolute desktop px max-widths (measured pins win over `maxRatio`). */
69
+ rungMaxWidths?: Partial<Record<TileSizeRung, number>>;
70
+ };
71
+ export declare const tileSizeStepExponents: Record<TileSizeRung, number>;
72
+ /** Lossless relative table: the measured 104/220/320/480/720 ramp at baseWidth 320. */
73
+ export declare const defaultWidthFactors: Record<TileSizeRung, number>;
74
+ export declare const defaultTileSizeInputs: TileSizeInputs;
75
+ /**
76
+ * Deep copy that PRESERVES partiality: keys the caller never set stay
77
+ * unset, so a later `resolveTileSizeInputs` still sees them as defaults
78
+ * rather than explicit undefineds.
79
+ */
80
+ export declare function cloneTileSizeInputs<T extends Partial<TileSizeInputs>>(inputs: T): T;
81
+ /**
82
+ * Fill defaults. `{ baseWidth: 280 }` (any scaling override without explicit
83
+ * tables) drops the measured rungWidths/widthFactors/rungMaxWidths so every
84
+ * rung regenerates from `stepRatio` — the `resolveSizeRecipeInputs` contract.
85
+ */
86
+ export declare function resolveTileSizeInputs(partial?: Partial<TileSizeInputs>): TileSizeInputs;
87
+ /** Derive the full ladder from resolved inputs. Tables are generated, never handwritten. */
88
+ export declare function generateTileSizeLadder(inputs: TileSizeInputs): TileSizeLadder;
89
+ export type TileRungFlexProps = {
90
+ flexGrow: number;
91
+ flexBasis: number;
92
+ minWidth: number;
93
+ maxWidth: number;
94
+ };
95
+ /**
96
+ * Style fragment for a tile in a wrapping flex wall (the Lookout idiom:
97
+ * tiles grow from `minWidth` and stop at `maxWidth`). Spread it on the tile
98
+ * surface; gutters stay on the container's space-knob gap.
99
+ */
100
+ export declare function tileRungFlexProps(stop: TileSizeStop): TileRungFlexProps;
101
+ /**
102
+ * Column count for a fixed-column tile grid (the ImageGrid / springboard
103
+ * idiom): as many columns as fit at the rung's minWidth. `gutter` is the
104
+ * inter-column gap (the space knob's value), never baked into the rung.
105
+ *
106
+ * The count is min-driven on purpose: adding a column to honor `maxWidth`
107
+ * would, by construction of the fit, drop every cell below `minWidth`
108
+ * (`(fit+1)` cells at minWidth already overflow the width). `maxWidth`
109
+ * binds in the wrapping-flex idiom (`tileRungFlexProps`), where a tile can
110
+ * stop growing without forcing a new column.
111
+ *
112
+ * At the springboard's own numbers this reproduces the launcher's split:
113
+ * `xs` (104) with the 8px launcher gutter yields 6 columns at the 700px
114
+ * wide-breakpoint and 4 in the phone band.
115
+ */
116
+ export declare function tileColumnsForWidth(width: number, stop: TileSizeStop, gutter?: number): number;
117
+ /**
118
+ * Named media aspect ratios (width / height). A skeleton twin drawn at the
119
+ * wrong aspect produces the exact layout jump it exists to prevent
120
+ * (ImageGrid hardcoded 1; Lookout frames are 16:10).
121
+ */
122
+ export declare const mediaAspectRatios: {
123
+ /** Album art, avatars, icon plates. */
124
+ readonly square: 1;
125
+ /** Stills cameras, 4:3. */
126
+ readonly photo: number;
127
+ /** Desktop screen captures, 16:10 — the Lookout frame shape. */
128
+ readonly screen: number;
129
+ /** Video stills, 16:9. */
130
+ readonly video: number;
131
+ /** Portrait media, 3:4. */
132
+ readonly portrait: number;
133
+ };
134
+ export type MediaAspectName = keyof typeof mediaAspectRatios;
135
+ /** A named aspect or an explicit width/height number. */
136
+ export type MediaAspect = MediaAspectName | number;
137
+ /** Resolve a name or number to a positive width/height ratio (default `square`). */
138
+ export declare function resolveMediaAspect(aspect?: MediaAspect | null): number;
139
+ //# sourceMappingURL=tileSizes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tileSizes.d.ts","sourceRoot":"","sources":["../../src/theme/tileSizes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAIH,eAAO,MAAM,aAAa,yCAA0C,CAAC;AAErE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,iEAAiE;AACjE,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;AAIhE,MAAM,MAAM,cAAc,GAAG;IAC3B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;CACvD,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAM9D,CAAC;AAeF,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAM5D,CAAC;AAYF,eAAO,MAAM,qBAAqB,EAAE,cASnC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,OAAO,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAOnF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,OAAO,CAAC,cAAc,CAAM,GAAG,cAAc,CAiC3F;AAiBD,4FAA4F;AAC5F,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAY7E;AAID,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,iBAAiB,CAOvE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,SAAI,GAAG,MAAM,CAGzF;AAID;;;;GAIG;AACH,eAAO,MAAM,iBAAiB;IAC5B,uCAAuC;;IAEvC,2BAA2B;;IAE3B,gEAAgE;;IAEhE,0BAA0B;;IAE1B,2BAA2B;;CAEnB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,iBAAiB,CAAC;AAE7D,yDAAyD;AACzD,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,MAAM,CAAC;AAEnD,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,MAAM,CAMtE"}