@yschimke/compose-design-map 1.53.1 → 1.55.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 CHANGED
@@ -156,6 +156,23 @@ one-to-one onto distinct device widths are a size axis and one of them can be pi
156
156
  - the sizes the base did not take **fold under it as cells**, seeded `breakpoint=<dp>` and named
157
157
  `<dp>dp`, so they are published rather than discarded.
158
158
 
159
+ A bare `breakpoint=<dp>` is a value no kit vocabulary contains, so such a cell resolves against
160
+ nothing — correctly, for the majority of kits, which draw every screen cell at one size and have no
161
+ size axis at all. Where a kit *does* publish screen size as a variant property, the component says
162
+ so:
163
+
164
+ ```kotlin
165
+ @CatalogComponent(id = "Picker", breakpointKit = ["225=Larger Screen (BP)=Yes"])
166
+ ```
167
+
168
+ and the 225dp cell is seeded `breakpoint=225` with `kitAxis`/`kitValue` attached, pairing with the
169
+ kit node the picture was always there for. It is a per-component declaration rather than a
170
+ per-run flag because it is a property of one component's kit set, not of the catalog; a size the
171
+ component never draws, or a malformed entry, keeps the bare seed and is reported unresolved rather
172
+ than mispaired. A breakpoint capture is the one kind of cell that cannot carry `@OverrideVariant
173
+ (kitAxis = …)` itself — it is not an annotation at all — which is why the mapping lives on the
174
+ component.
175
+
159
176
  Two captures of the *same* width are still a mode, whatever devices they name: nothing orders them,
160
177
  so they stay `ambiguousMode`. An `@OverrideVariant` cell rides the base breakpoint only — the
161
178
  product of both axes would multiply the sheet by every size, and the base carries the matrix.
package/design-map.mjs CHANGED
@@ -503,7 +503,61 @@ export function declarationMisses(preview) {
503
503
  * this projection, which is why a FAB size axis read as unauthored while `FabSmall`/`FabMedium`/
504
504
  * `FabLarge` sat in the catalog all along.
505
505
  */
506
- export function variantRendersByComponent(previews, selection = selectCaptures(previews)) {
506
+ /**
507
+ * The kit axis/value a component declares for a non-base breakpoint width, or `null`.
508
+ *
509
+ * [entries] are `@CatalogComponent.breakpointKit` strings, `"<widthDp>=<kitAxis>=<kitValue>"`. The
510
+ * `kitValue` half may itself contain `=` (a kit is free to name a cell `Size=Large`), so the split
511
+ * is on the FIRST TWO separators only — `String.split("=")` with a limit would drop the tail.
512
+ *
513
+ * A malformed entry, a non-numeric width or a width this component never renders yields `null`,
514
+ * which restores the bare `breakpoint=<width>` seed. That is the honest degradation — a mistyped
515
+ * mapping should cost the pairing, not silently pair the render against the wrong kit cell — but
516
+ * it must not be *silent*: degrading quietly leaves a typo indistinguishable from a deliberately
517
+ * undeclared mapping, and the only symptom is a generic pairing miss much further downstream. So
518
+ * a malformed entry is pushed onto [malformed] and reported as `diagnostics.invalidBreakpointKit`,
519
+ * the same way an unresolvable mode is reported rather than guessed at.
520
+ *
521
+ * "Malformed" is only about the entry's *shape*. An entry that is well-formed but names a width
522
+ * this particular capture is not — the common case, since one declaration serves every size — is
523
+ * not a fault and is not reported.
524
+ */
525
+ export function breakpointKitNames(entries, widthDp, malformed = []) {
526
+ for (const entry of entries ?? []) {
527
+ if (typeof entry !== "string") {
528
+ malformed.push({ entry: String(entry), reason: "not a string" });
529
+ continue;
530
+ }
531
+ const firstEq = entry.indexOf("=");
532
+ const secondEq = firstEq < 0 ? -1 : entry.indexOf("=", firstEq + 1);
533
+ if (secondEq < 0) {
534
+ malformed.push({ entry, reason: "expected <widthDp>=<kitAxis>=<kitValue>" });
535
+ continue;
536
+ }
537
+ const rawWidth = entry.slice(0, firstEq).trim();
538
+ const width = Number(rawWidth);
539
+ if (rawWidth === "" || !Number.isFinite(width)) {
540
+ malformed.push({ entry, reason: `width '${rawWidth}' is not a number` });
541
+ continue;
542
+ }
543
+ const kitAxis = entry.slice(firstEq + 1, secondEq).trim();
544
+ const kitValue = entry.slice(secondEq + 1).trim();
545
+ if (!kitAxis || !kitValue) {
546
+ malformed.push({ entry, reason: "kitAxis and kitValue must both be non-empty" });
547
+ continue;
548
+ }
549
+ // Well-formed but for another size: not a fault, and the overwhelmingly common case.
550
+ if (width !== widthDp) continue;
551
+ return { kitAxis, kitValue };
552
+ }
553
+ return null;
554
+ }
555
+
556
+ export function variantRendersByComponent(
557
+ previews,
558
+ selection = selectCaptures(previews),
559
+ invalidBreakpointKit = [],
560
+ ) {
507
561
  const byComponent = new Map();
508
562
  for (const preview of previews) {
509
563
  const catalog = preview.catalog;
@@ -535,7 +589,28 @@ export function variantRendersByComponent(previews, selection = selectCaptures(p
535
589
  if (!isOverrideVariant && !isCatalogVariant) {
536
590
  const widthDp = catalog.role === "COMPONENT" ? selection.breakpointOf(preview) : null;
537
591
  if (widthDp === null) continue;
538
- const seeds = [{ key: "breakpoint", raw: String(widthDp) }];
592
+ // `@CatalogComponent(breakpointKit = ["225=Larger Screen (BP)=Yes"])` says what this size
593
+ // MEANS to the kit. Without it the seed is a bare `breakpoint=225` — a value no kit
594
+ // vocabulary contains — and the resolver can only report "no counterpart for
595
+ // `breakpoint=225`", even where the kit publishes the very cells the render would pair with
596
+ // (issue #4827).
597
+ //
598
+ // Opt-in and per component, because most kits draw every screen cell at one size and have no
599
+ // size axis at all. Declaring nothing keeps the bare seed, so those captures stay honestly
600
+ // reported as renders with no kit counterpart rather than mispaired.
601
+ const malformed = [];
602
+ const kit = breakpointKitNames(catalog.breakpointKit, widthDp, malformed);
603
+ for (const bad of malformed) {
604
+ invalidBreakpointKit.push({ componentId: catalog.componentId, ...bad });
605
+ }
606
+ const seeds = [
607
+ {
608
+ key: "breakpoint",
609
+ raw: String(widthDp),
610
+ ...(kit?.kitAxis ? { kitAxis: kit.kitAxis } : {}),
611
+ ...(kit?.kitValue ? { kitValue: kit.kitValue } : {}),
612
+ },
613
+ ];
539
614
  const list = byComponent.get(catalog.componentId) ?? [];
540
615
  list.push({ previewId: preview.id, name: `${widthDp}dp`, seeds });
541
616
  byComponent.set(catalog.componentId, list);
@@ -587,7 +662,8 @@ export function variantRendersByComponent(previews, selection = selectCaptures(p
587
662
  */
588
663
  export function projectDesignMap(previews, opts = {}) {
589
664
  const selection = selectCaptures(previews, { baseBreakpointDp: opts.baseBreakpointDp });
590
- const variantRenders = variantRendersByComponent(previews, selection);
665
+ const invalidBreakpointKit = [];
666
+ const variantRenders = variantRendersByComponent(previews, selection, invalidBreakpointKit);
591
667
 
592
668
  const components = [];
593
669
  const declarations = [];
@@ -752,6 +828,16 @@ export function projectDesignMap(previews, opts = {}) {
752
828
  !referencelessSubjects.has(a.subject) &&
753
829
  (!a.componentIds.length || a.componentIds.some((id) => !referencelessIds.has(id))),
754
830
  ),
831
+ // `@CatalogComponent(breakpointKit = …)` entries that do not parse. Reported rather than
832
+ // dropped: the degradation is to the bare `breakpoint=<dp>` seed, which is exactly what an
833
+ // undeclared component produces, so a typo would otherwise be invisible until someone
834
+ // wondered why a kit cell never paired. Deduplicated — one declaration is re-read once per
835
+ // non-base capture of the component, and the same typo is one fault, not four.
836
+ invalidBreakpointKit: [
837
+ ...new Map(
838
+ invalidBreakpointKit.map((e) => [`${e.componentId}\u0000${e.entry}`, e]),
839
+ ).values(),
840
+ ],
755
841
  variantRenders: declarations.reduce((n, d) => n + d.renders.length, 0),
756
842
  withSet: components.filter((c) => c.refSet).length,
757
843
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yschimke/compose-design-map",
3
- "version": "1.53.1",
3
+ "version": "1.55.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",