@yschimke/compose-design-map 1.11.1 → 1.13.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/README.md +24 -1
- package/design-map.mjs +116 -11
- package/emit-design-map.mjs +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Every field the projection reads is defined in this repository:
|
|
|
26
26
|
| Field on `previews.json` | Declared by |
|
|
27
27
|
| --- | --- |
|
|
28
28
|
| `catalog.reference`, `referenceSet`, `noReference`, `referenceContentsOnly`, `kitAxis` | [`@CatalogComponent`](https://github.com/yschimke/compose-ai-tools/blob/main/api/preview-annotations/src/commonMain/kotlin/ee/schimke/composeai/preview/CatalogComponent.kt) |
|
|
29
|
-
| `catalog.props`, `catalog.state` | `@CatalogVariant` |
|
|
29
|
+
| `catalog.props`, `catalog.state`, `catalog.kitValue` | `@CatalogVariant` |
|
|
30
30
|
| `overrides.seeds`, `overrides.props` | `@OverrideVariant` / `@PreviewAxis` |
|
|
31
31
|
| `overrides.kitAxis`, `overrides.kitValue` | `@OverrideVariant` |
|
|
32
32
|
|
|
@@ -67,6 +67,29 @@ Figma concern, it needs a Figma credential to derive, and it differs per kit —
|
|
|
67
67
|
repo has any business holding. So the variant renders come out as **declarations** and a resolver
|
|
68
68
|
that owns a kit index turns them into node ids.
|
|
69
69
|
|
|
70
|
+
### When the catalog knows the kit's word for it
|
|
71
|
+
|
|
72
|
+
Some values no translation table reaches: the Material 3 kit files one date-picker variant as
|
|
73
|
+
`Type=Full-screen (range)`, and `type=range` finds nothing against it. `kitAxis` / `kitValue` are
|
|
74
|
+
how a variant names both sides — the Compose word in `props`/`strings`, the kit's word beside it —
|
|
75
|
+
and this projection carries them onto the seed so the resolver can prefer them over its own tables:
|
|
76
|
+
|
|
77
|
+
```kotlin
|
|
78
|
+
@CatalogVariant(of = "DatePicker/Modal", props = ["type=range"],
|
|
79
|
+
kitAxis = "Type", kitValue = "Full-screen (range)")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```jsonc
|
|
83
|
+
{ "seeds": [{ "key": "type", "raw": "range",
|
|
84
|
+
"kitAxis": "Type", "kitValue": "Full-screen (range)" }] }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Projecting is not translating: nothing here checks a declaration against a kit, because there is no
|
|
88
|
+
kit here to check against. The one thing it does judge is whether the declaration can be *placed* —
|
|
89
|
+
the annotation carries one pair per variant, so a cell seeding two knobs gives no way to say which
|
|
90
|
+
one the axis names. Those are reported and dropped rather than guessed at, since guessing pins the
|
|
91
|
+
wrong axis and resolves, confidently, to the wrong node.
|
|
92
|
+
|
|
70
93
|
The two halves are separable because the first is useful alone: a repo with no kit index still gets
|
|
71
94
|
a valid map of base references, which is most of the value at none of the cost.
|
|
72
95
|
|
package/design-map.mjs
CHANGED
|
@@ -34,6 +34,13 @@
|
|
|
34
34
|
* [`@design-parity/kit-index`](https://github.com/yschimke/design-parity/tree/main/packages/kit-index)
|
|
35
35
|
* does hold.
|
|
36
36
|
*
|
|
37
|
+
* What it does carry across is the catalog's own statement about the kit, when it makes one:
|
|
38
|
+
* `@OverrideVariant(kitAxis = "Show avatar", kitValue = "True")` names the kit's spelling directly,
|
|
39
|
+
* and those names ride on the seed into the sidecar for a resolver to prefer over its alias tables.
|
|
40
|
+
* Projecting them is not translating them — this module never checks a declaration against a kit,
|
|
41
|
+
* because it has no kit to check against; it only puts the author's words where the resolver can
|
|
42
|
+
* read them. They were dead metadata until it did (compose-ai-tools#4086).
|
|
43
|
+
*
|
|
37
44
|
* So the variant renders come out as **declarations**, in a sidecar
|
|
38
45
|
* ({@link DESIGN_MAP_VARIANTS_SCHEMA}): "this preview is the same component with these knobs
|
|
39
46
|
* turned". A resolver that owns a kit index turns each into a tagged `ref`/`previewId` pair beside
|
|
@@ -98,6 +105,35 @@ export function sourceForRef(ref) {
|
|
|
98
105
|
* its non-default seeds is missing the axes it happens to sit at, and a kit that spells its default
|
|
99
106
|
* size explicitly in a combination cell then has nothing to match against.
|
|
100
107
|
*/
|
|
108
|
+
/**
|
|
109
|
+
* Attach a kit-side declaration to the one seed it can belong to.
|
|
110
|
+
*
|
|
111
|
+
* `kitAxis` / `kitValue` name the design kit's own spelling for *a* knob — `content=avatar` is the
|
|
112
|
+
* kit's `Show avatar=True` — and the annotation carries one pair per variant, so a variant that
|
|
113
|
+
* turns two knobs gives no way to say which of them the axis names. It attaches to a lone seed;
|
|
114
|
+
* with several, the declaration is reported and dropped rather than guessed at, since guessing
|
|
115
|
+
* would pin the wrong axis and resolve to a confidently wrong node.
|
|
116
|
+
*
|
|
117
|
+
* A `null` declaration (neither field) leaves the seeds exactly as they were, which is every
|
|
118
|
+
* variant written before these fields existed.
|
|
119
|
+
*/
|
|
120
|
+
function declareKitNames(seeds, kitAxis, kitValue) {
|
|
121
|
+
if (!kitAxis && !kitValue) return { seeds, unattached: [] };
|
|
122
|
+
if (seeds.length !== 1) {
|
|
123
|
+
return { seeds, unattached: [{ kitAxis, kitValue, seeds: seeds.map((s) => s.key) }] };
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
seeds: [
|
|
127
|
+
{
|
|
128
|
+
...seeds[0],
|
|
129
|
+
...(kitAxis ? { kitAxis } : {}),
|
|
130
|
+
...(kitValue ? { kitValue } : {}),
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
unattached: [],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
101
137
|
function foldSeeds(catalog) {
|
|
102
138
|
// `props` names the axis; `state` is the annotation's shorthand for the one axis common enough
|
|
103
139
|
// to have its own parameter. Either is a declaration, so neither is inferred —
|
|
@@ -107,11 +143,15 @@ function foldSeeds(catalog) {
|
|
|
107
143
|
if (catalog.state && !props.some((p) => p.key === "state")) {
|
|
108
144
|
props.push({ key: "state", value: catalog.state });
|
|
109
145
|
}
|
|
110
|
-
return
|
|
146
|
+
return declareKitNames(
|
|
147
|
+
props.map((p) => ({ key: p.key, raw: p.value })),
|
|
148
|
+
catalog.kitAxis,
|
|
149
|
+
catalog.kitValue,
|
|
150
|
+
);
|
|
111
151
|
}
|
|
112
152
|
|
|
113
|
-
function cellSeeds(overrides) {
|
|
114
|
-
if (!overrides) return [];
|
|
153
|
+
function cellSeeds(overrides, catalog) {
|
|
154
|
+
if (!overrides) return { seeds: [], unattached: [] };
|
|
115
155
|
|
|
116
156
|
const seeds = overrides.props?.length
|
|
117
157
|
? overrides.props.map((p) => ({ key: p.key, raw: p.value }))
|
|
@@ -124,16 +164,42 @@ function cellSeeds(overrides) {
|
|
|
124
164
|
// that knob already has. Without this the variant declares nothing: `seeds` is empty, an empty
|
|
125
165
|
// vector matches every sibling, and the render is dropped as "names no axis".
|
|
126
166
|
const interaction = overrides.interaction;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
167
|
+
const drivenState =
|
|
168
|
+
interaction && interaction !== "None" && !seeds.some((s) => s.key === "state")
|
|
169
|
+
? { key: "state", raw: String(interaction).toLowerCase() }
|
|
170
|
+
: undefined;
|
|
171
|
+
|
|
172
|
+
// A COMPONENT's own `kitAxis` is a DEFAULT for its cells — "every variant of this one turns the
|
|
173
|
+
// same kit property" — so a cell that names its own axis wins, and a cell that names only its
|
|
174
|
+
// exceptional VALUE still inherits the axis, which is the case the default exists for. A default
|
|
175
|
+
// that cannot be placed is silent, where the explicit form is reported: one is a blanket that
|
|
176
|
+
// need not cover everything, the other is an assertion about this cell that could not be
|
|
177
|
+
// honoured.
|
|
178
|
+
const componentDefault = catalog?.role === "COMPONENT" ? catalog.kitAxis : undefined;
|
|
179
|
+
const axis = overrides.kitAxis ?? componentDefault;
|
|
180
|
+
const explicit = Boolean(overrides.kitAxis || overrides.kitValue);
|
|
181
|
+
|
|
182
|
+
// The interaction axis is not a knob anybody seeded — the harness drives it — so it does not
|
|
183
|
+
// count towards "which knob does this declaration name". A cell seeding `size=l` and pressing
|
|
184
|
+
// the component still names one knob, and its declaration belongs to that one. Only an
|
|
185
|
+
// interaction-only cell has the state seed as its subject.
|
|
186
|
+
const declarable = seeds.length ? seeds : drivenState ? [drivenState] : [];
|
|
187
|
+
const declared = explicit
|
|
188
|
+
? declareKitNames(declarable, axis, overrides.kitValue)
|
|
189
|
+
: axis && declarable.length === 1
|
|
190
|
+
? declareKitNames(declarable, axis, undefined)
|
|
191
|
+
: { seeds: declarable, unattached: [] };
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
seeds: seeds.length && drivenState ? [...declared.seeds, drivenState] : declared.seeds,
|
|
195
|
+
unattached: declared.unattached,
|
|
196
|
+
};
|
|
131
197
|
}
|
|
132
198
|
|
|
133
199
|
export function variantSeeds(preview) {
|
|
134
200
|
const catalog = preview.catalog;
|
|
135
|
-
const fold = catalog?.role === "VARIANT" ? foldSeeds(catalog) : [];
|
|
136
|
-
const cell = cellSeeds(preview.overrides);
|
|
201
|
+
const { seeds: fold } = catalog?.role === "VARIANT" ? foldSeeds(catalog) : { seeds: [] };
|
|
202
|
+
const { seeds: cell } = cellSeeds(preview.overrides, catalog);
|
|
137
203
|
if (!fold.length) return cell;
|
|
138
204
|
if (!cell.length) return fold;
|
|
139
205
|
|
|
@@ -144,8 +210,17 @@ export function variantSeeds(preview) {
|
|
|
144
210
|
//
|
|
145
211
|
// The CELL wins a key collision. Both describe the same render, but the cell's value is what the
|
|
146
212
|
// renderer actually seeded, and the fold's is the default it seeded over.
|
|
213
|
+
//
|
|
214
|
+
// Its kit AXIS survives that, though. The axis is a fact about the knob — what the kit calls the
|
|
215
|
+
// thing being turned — and the collision only changes which way it is turned, so dropping the
|
|
216
|
+
// fold's seed wholesale would lose the one name a resolver cannot work out for itself, silently.
|
|
217
|
+
// The fold's `kitValue` does not survive: it described the value the cell has just replaced.
|
|
218
|
+
const foldAxes = new Map(fold.filter((s) => s.kitAxis).map((s) => [s.key, s.kitAxis]));
|
|
219
|
+
const merged = cell.map((s) =>
|
|
220
|
+
!s.kitAxis && foldAxes.has(s.key) ? { ...s, kitAxis: foldAxes.get(s.key) } : s,
|
|
221
|
+
);
|
|
147
222
|
const seeded = new Set(cell.map((s) => s.key));
|
|
148
|
-
return [...fold.filter((s) => !seeded.has(s.key)), ...
|
|
223
|
+
return [...fold.filter((s) => !seeded.has(s.key)), ...merged];
|
|
149
224
|
}
|
|
150
225
|
|
|
151
226
|
/** The name a variant render goes by, for a report and for the design-map `state` slot. */
|
|
@@ -156,12 +231,31 @@ function variantName(preview, seeds) {
|
|
|
156
231
|
// Named for the FOLD's own axis, not for the merged vector — `wave`, not `wave-1.0` — so a
|
|
157
232
|
// folded component's cells read as `wave-full` / `wave-quarter` under it, the same shape a
|
|
158
233
|
// top-level component's cells have.
|
|
159
|
-
const fold =
|
|
234
|
+
const fold =
|
|
235
|
+
catalog.state ??
|
|
236
|
+
foldSeeds(catalog)
|
|
237
|
+
.seeds.map((s) => s.raw)
|
|
238
|
+
.join("-");
|
|
160
239
|
return cell ? `${fold}-${cell}` : fold;
|
|
161
240
|
}
|
|
162
241
|
return cell ?? seeds.map((s) => `${s.key}=${s.raw}`).join(", ");
|
|
163
242
|
}
|
|
164
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Declarations this render could not place on a seed, one entry per variant that names a kit axis
|
|
246
|
+
* or value it has more than one knob to hang it on.
|
|
247
|
+
*
|
|
248
|
+
* Reported rather than silently dropped: somebody took the trouble to spell the kit's own name,
|
|
249
|
+
* and silence would leave them believing the render compares against a node it never reached —
|
|
250
|
+
* the exact failure `kitAxis` exists to remove.
|
|
251
|
+
*/
|
|
252
|
+
export function declarationMisses(preview) {
|
|
253
|
+
const catalog = preview.catalog;
|
|
254
|
+
const fold = catalog?.role === "VARIANT" ? foldSeeds(catalog).unattached : [];
|
|
255
|
+
const cell = cellSeeds(preview.overrides, catalog).unattached;
|
|
256
|
+
return [...fold, ...cell].map((miss) => ({ previewId: preview.id, ...miss }));
|
|
257
|
+
}
|
|
258
|
+
|
|
165
259
|
/**
|
|
166
260
|
* Every variant render, grouped by the component it folds under.
|
|
167
261
|
*
|
|
@@ -275,12 +369,23 @@ export function projectDesignMap(previews, opts = {}) {
|
|
|
275
369
|
unmapped.sort();
|
|
276
370
|
statedAbsent.sort((a, b) => a.componentId.localeCompare(b.componentId));
|
|
277
371
|
|
|
372
|
+
// Only the captures that participate: a variant declares once, and reporting its dark capture
|
|
373
|
+
// beside its light one would double every line of a list that exists to be acted on.
|
|
374
|
+
const unplacedDeclarations = previews
|
|
375
|
+
.filter(
|
|
376
|
+
(preview) =>
|
|
377
|
+
preview.catalog &&
|
|
378
|
+
(LIGHT_CAPTURE.test(preview.id) || LIGHT_VARIANT_CAPTURE.test(preview.id)),
|
|
379
|
+
)
|
|
380
|
+
.flatMap(declarationMisses);
|
|
381
|
+
|
|
278
382
|
return {
|
|
279
383
|
map: { components },
|
|
280
384
|
variants: { schema: DESIGN_MAP_VARIANTS_SCHEMA, components: declarations },
|
|
281
385
|
diagnostics: {
|
|
282
386
|
unmapped,
|
|
283
387
|
statedAbsent,
|
|
388
|
+
unplacedDeclarations,
|
|
284
389
|
variantRenders: declarations.reduce((n, d) => n + d.renders.length, 0),
|
|
285
390
|
withSet: components.filter((c) => c.refSet).length,
|
|
286
391
|
},
|
package/emit-design-map.mjs
CHANGED
|
@@ -144,6 +144,24 @@ if (diagnostics.statedAbsent.length) {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
if (diagnostics.unplacedDeclarations?.length) {
|
|
148
|
+
console.log(
|
|
149
|
+
`\n${diagnostics.unplacedDeclarations.length} variant(s) name a kit axis or value that could ` +
|
|
150
|
+
`not be placed: the annotation carries one kitAxis/kitValue and the variant turns more than ` +
|
|
151
|
+
`one knob, so which knob the axis names is undeclared. Split the cell, or drop the kit ` +
|
|
152
|
+
`names and let the resolver match on the knob's own spelling:`,
|
|
153
|
+
);
|
|
154
|
+
for (const miss of diagnostics.unplacedDeclarations) {
|
|
155
|
+
const named = [
|
|
156
|
+
miss.kitAxis ? `kitAxis = "${miss.kitAxis}"` : null,
|
|
157
|
+
miss.kitValue ? `kitValue = "${miss.kitValue}"` : null,
|
|
158
|
+
]
|
|
159
|
+
.filter(Boolean)
|
|
160
|
+
.join(", ");
|
|
161
|
+
console.log(` - ${miss.previewId} — ${named} against ${miss.seeds.join(", ")}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
147
165
|
if (diagnostics.unmapped.length) {
|
|
148
166
|
console.log(
|
|
149
167
|
`\n${diagnostics.unmapped.length} component(s) carry neither ` +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yschimke/compose-design-map",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Project a compose-preview discovery manifest into design-parity's design-map.json, plus a sidecar of unresolved variant declarations. Dependency-free.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|