@bettercms-ai/kit 2.0.0 → 2.0.1

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.
Files changed (120) hide show
  1. package/dist/adapters/lossy.d.ts +12 -4
  2. package/dist/adapters/lossy.js +19 -8
  3. package/dist/adapters/lossy.js.map +1 -1
  4. package/dist/adapters/prod-types.d.ts +46 -14
  5. package/dist/adapters/prod-types.js +37 -35
  6. package/dist/adapters/prod-types.js.map +1 -1
  7. package/dist/adapters/schema.d.ts +39 -0
  8. package/dist/adapters/schema.js +281 -82
  9. package/dist/adapters/schema.js.map +1 -1
  10. package/dist/context.d.ts +39 -0
  11. package/dist/context.js +40 -1
  12. package/dist/context.js.map +1 -1
  13. package/dist/controls/form-picker-register.d.ts +7 -1
  14. package/dist/controls/form-picker-register.js +1 -5
  15. package/dist/controls/form-picker-register.js.map +1 -1
  16. package/dist/controls/icon.d.ts +1 -1
  17. package/dist/controls/icon.js +1 -1
  18. package/dist/controls/icon.js.map +1 -1
  19. package/dist/controls/link.d.ts +11 -4
  20. package/dist/controls/link.js +120 -202
  21. package/dist/controls/link.js.map +1 -1
  22. package/dist/controls/location.js +3 -8
  23. package/dist/controls/location.js.map +1 -1
  24. package/dist/controls/media.d.ts +0 -14
  25. package/dist/controls/media.js +52 -95
  26. package/dist/controls/media.js.map +1 -1
  27. package/dist/controls/reference-services.d.ts +23 -2
  28. package/dist/controls/reference.d.ts +2 -2
  29. package/dist/controls/reference.js +79 -28
  30. package/dist/controls/reference.js.map +1 -1
  31. package/dist/controls/rich-text/article-shell.d.ts +145 -0
  32. package/dist/controls/rich-text/article-shell.js +514 -0
  33. package/dist/controls/rich-text/article-shell.js.map +1 -0
  34. package/dist/controls/rich-text/author.d.ts +25 -0
  35. package/dist/controls/rich-text/author.js +107 -0
  36. package/dist/controls/rich-text/author.js.map +1 -0
  37. package/dist/controls/rich-text/blocks.d.ts +19 -2
  38. package/dist/controls/rich-text/blocks.js +275 -638
  39. package/dist/controls/rich-text/blocks.js.map +1 -1
  40. package/dist/controls/rich-text/button.d.ts +23 -0
  41. package/dist/controls/rich-text/button.js +81 -0
  42. package/dist/controls/rich-text/button.js.map +1 -0
  43. package/dist/controls/rich-text/catalog.d.ts +2 -5
  44. package/dist/controls/rich-text/catalog.js +121 -7
  45. package/dist/controls/rich-text/catalog.js.map +1 -1
  46. package/dist/controls/rich-text/checklist.d.ts +24 -0
  47. package/dist/controls/rich-text/checklist.js +115 -0
  48. package/dist/controls/rich-text/checklist.js.map +1 -0
  49. package/dist/controls/rich-text/codec.d.ts +15 -1
  50. package/dist/controls/rich-text/codec.js +11 -1
  51. package/dist/controls/rich-text/codec.js.map +1 -1
  52. package/dist/controls/rich-text/columns.d.ts +41 -0
  53. package/dist/controls/rich-text/columns.js +181 -0
  54. package/dist/controls/rich-text/columns.js.map +1 -0
  55. package/dist/controls/rich-text/control.js +1 -0
  56. package/dist/controls/rich-text/control.js.map +1 -1
  57. package/dist/controls/rich-text/dom.d.ts +84 -4
  58. package/dist/controls/rich-text/dom.js +171 -10
  59. package/dist/controls/rich-text/dom.js.map +1 -1
  60. package/dist/controls/rich-text/editor.d.ts +4 -2
  61. package/dist/controls/rich-text/editor.js +199 -26
  62. package/dist/controls/rich-text/editor.js.map +1 -1
  63. package/dist/controls/rich-text/embed.d.ts +28 -0
  64. package/dist/controls/rich-text/embed.js +210 -0
  65. package/dist/controls/rich-text/embed.js.map +1 -0
  66. package/dist/controls/rich-text/footnote.d.ts +41 -0
  67. package/dist/controls/rich-text/footnote.js +185 -0
  68. package/dist/controls/rich-text/footnote.js.map +1 -0
  69. package/dist/controls/rich-text/gallery.d.ts +28 -0
  70. package/dist/controls/rich-text/gallery.js +167 -0
  71. package/dist/controls/rich-text/gallery.js.map +1 -0
  72. package/dist/controls/rich-text/link-form.d.ts +13 -1
  73. package/dist/controls/rich-text/link-form.js +41 -7
  74. package/dist/controls/rich-text/link-form.js.map +1 -1
  75. package/dist/controls/rich-text/ops.js +6 -0
  76. package/dist/controls/rich-text/ops.js.map +1 -1
  77. package/dist/controls/rich-text/related.d.ts +49 -0
  78. package/dist/controls/rich-text/related.js +233 -0
  79. package/dist/controls/rich-text/related.js.map +1 -0
  80. package/dist/controls/rich-text/toc.d.ts +47 -0
  81. package/dist/controls/rich-text/toc.js +160 -0
  82. package/dist/controls/rich-text/toc.js.map +1 -0
  83. package/dist/controls/slot-register.d.ts +6 -1
  84. package/dist/controls/slot-register.js +1 -4
  85. package/dist/controls/slot-register.js.map +1 -1
  86. package/dist/core/core.d.ts +11 -3
  87. package/dist/core/core.js +24 -6
  88. package/dist/core/core.js.map +1 -1
  89. package/dist/core/facets.d.ts +16 -0
  90. package/dist/core/facets.js +17 -1
  91. package/dist/core/facets.js.map +1 -1
  92. package/dist/core/index.js +2 -2
  93. package/dist/index.js +16 -5
  94. package/dist/index.js.map +1 -1
  95. package/dist/ledgers/exclusions.v1.js +141 -0
  96. package/dist/ledgers/exclusions.v1.js.map +1 -0
  97. package/dist/library/coverage.d.ts +13 -1
  98. package/dist/library/coverage.js +12 -1
  99. package/dist/library/coverage.js.map +1 -1
  100. package/dist/library/kit-catalog.json +156 -153
  101. package/dist/schema/types.d.ts +17 -0
  102. package/dist/schema/types.js.map +1 -1
  103. package/dist/styles.css +1 -1
  104. package/dist/testing/coverage-extras.d.ts +8 -0
  105. package/dist/testing/coverage-extras.js +44 -0
  106. package/dist/testing/coverage-extras.js.map +1 -0
  107. package/dist/testing/exclusions.d.ts +30 -0
  108. package/dist/testing/exclusions.js +13 -0
  109. package/dist/testing/exclusions.js.map +1 -0
  110. package/dist/ui/autocomplete.d.ts +29 -1
  111. package/dist/ui/autocomplete.js +102 -47
  112. package/dist/ui/autocomplete.js.map +1 -1
  113. package/dist/ui/button.js +3 -5
  114. package/dist/ui/button.js.map +1 -1
  115. package/dist/ui/index.js +2 -2
  116. package/dist/ui/overlay.d.ts +4 -1
  117. package/dist/ui/overlay.js +2 -1
  118. package/dist/ui/overlay.js.map +1 -1
  119. package/dist/ui/toast.js +1 -1
  120. package/package.json +8 -2
@@ -1,9 +1,10 @@
1
1
  import { CATALOG, FEATURES, isHostConfigKey } from "./lossy.js";
2
- import { CONTROL_TYPES, HELP_MORE_MAX, ITEM_NAME_RE, UI_CONTROLS } from "./prod-types.js";
2
+ import { CONTROL_TYPES, FIELDSET_DESCRIPTION_MAX, FIELD_HELP_MAX, HELP_MORE_MAX, ITEM_NAME_RE, MAX_FIELD_DEPTH, OPTION_DESCRIPTION_MAX, PROP_GROUP_DESCRIPTION_MAX, PROP_HELP_MAX, PROP_PLACEHOLDER_MAX, PROP_TAB_MAX, TAB_MAX as TAB_MAX$1, UI_CONTROLS, UI_PLACEHOLDER_MAX } from "./prod-types.js";
3
3
  import { dockIconOf, tablerIconOf } from "./icons.js";
4
4
  import { slotChildrenFromStored, slotChildrenToStored } from "../controls/slot-services.js";
5
5
  import { isDataTableProp, tableConfigFromOptions, tableOptionsFromConfig } from "../controls/table/prod.js";
6
6
  import { buttonFromProd, buttonToProd, datetimeFromProd, datetimeToProd, imageFromProd, imageToProd, isCropRect, linkFromProd, linkToProd, listFromProd, listToProd, ratingFromProd, ratingToProd, referenceFromProd, referenceToProd, scaleFromProd, scaleToProd, typedRowsFromProd, typedRowsToProd, videoFromProd, videoToProd, zoneListFromProd, zoneListToProd } from "./values.js";
7
+ import { BLOCK_FIELD_TYPES_RENDERABLE_NOW, KEY_FORMAT, KIT_CATEGORIES, PRIMITIVE_ARRAY_RULE, RICHTEXT_FEATURES, SEGMENTED_MAX, SEGMENTED_MIN, isReservedKey, normaliseRichTextFeatures } from "@bettercms-ai/types";
7
8
  //#region src/adapters/schema.ts
8
9
  var isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
9
10
  function deepEqual(a, b) {
@@ -182,8 +183,12 @@ var CATEGORY_TO_PROD = {
182
183
  Content: "content",
183
184
  Layout: "section"
184
185
  };
185
- /** The field types a kind:'block' model may hold (backend block-model-mapping.ts FIELD_PRIMITIVE). */
186
- var BLOCK_FIELD_TYPES = /* @__PURE__ */ new Set([
186
+ /** The field types a kind:'block' model holds today: the ones a block primitive renders (types, staged to G7). */
187
+ var BLOCK_FIELD_TYPES = new Set(BLOCK_FIELD_TYPES_RENDERABLE_NOW);
188
+ /** What a block row's cell may hold, and a primitive list's items (backend block-model-mapping.ts CELL_FIELD_TYPES, LIST_ITEM_TYPES). */
189
+ /** The block-variant select's key on a model (c2ee279dd: a top-level select, only beside v2.variants). */
190
+ var BLOCK_VARIANT_KEY = "_variant";
191
+ var BLOCK_CELL_TYPES = /* @__PURE__ */ new Set([
187
192
  "text",
188
193
  "longtext",
189
194
  "richtext",
@@ -200,6 +205,24 @@ var BLOCK_FIELD_TYPES = /* @__PURE__ */ new Set([
200
205
  "datetime",
201
206
  "boolean"
202
207
  ]);
208
+ var BLOCK_LIST_ITEM_TYPES = /* @__PURE__ */ new Set([
209
+ "text",
210
+ "number",
211
+ "date"
212
+ ]);
213
+ /**
214
+ * Whether a block model may hold this stored field (backend findUnmappableFields / containerProblem): its type has a
215
+ * primitive, and an object or array has rows of cells (an array no non-repeating fields), or lists text, numbers or dates.
216
+ */
217
+ function blockRenders(f) {
218
+ if (!BLOCK_FIELD_TYPES.has(f.type)) return false;
219
+ if (f.type !== "object" && f.type !== "array") return true;
220
+ const zones = f.config?.zones;
221
+ if (f.type === "array" && zones?.nonRepeatable?.length) return false;
222
+ const row = f.type === "object" ? f.fields ?? [] : zones ? zones.repeatable?.fields ?? [] : null;
223
+ if (row === null) return BLOCK_LIST_ITEM_TYPES.has(String(f.config?.itemType));
224
+ return row.length > 0 && row.every((c) => BLOCK_CELL_TYPES.has(c.type));
225
+ }
203
226
  /** A slot prop's config.minItems / maxItems cap (SLOT_ITEMS_CAP, componentPropDefSchema). */
204
227
  var SLOT_ITEMS_CAP = 50;
205
228
  /** A table prop's rows and an array prop's items: their config.minItems / maxItems cap (LIST_ITEMS_CAP). */
@@ -358,23 +381,26 @@ function shapeOf(f, lv, ctx, top, nest) {
358
381
  };
359
382
  return shape;
360
383
  }
361
- /** backend content.ts keyRuleProblem: keys a resolver would misread (KEY_RESERVED), and a key's format (KEY_FORMAT). */
362
- var MODEL_RESERVED_KEYS = /* @__PURE__ */ new Set([
363
- "derived",
364
- "layout",
365
- "page"
366
- ]);
367
- /** The key a model field is stored under: a reserved or malformed V2 name, renamed. */
368
- function modelKeyOf(name) {
384
+ /**
385
+ * The key a model field is stored under: a V2 name the API refuses (types' isReservedKey, the API lane: `derived`,
386
+ * `layout`, `page`, a forbidden key or a leading `_`; or one off KEY_FORMAT), renamed. V2 authoring follows the API
387
+ * lane, so `id` is a field key like any other (only the derive lane, which mints keys, reserves it).
388
+ */
389
+ /** The address-prefix keys a model may hold below its top level (019141ffc): they are no address there. */
390
+ var NESTED_OK_KEYS = /* @__PURE__ */ new Set(["layout", "page"]);
391
+ function modelKeyOf(name, nested = false) {
392
+ if (KEY_FORMAT.test(name) && (!isReservedKey(name) || nested && NESTED_OK_KEYS.has(name))) return name;
369
393
  let k = name.replace(/^_+/, "").replace(/[^A-Za-z0-9_-]/g, "");
370
394
  if (!/^[A-Za-z]/.test(k)) k = `f${k}`;
371
- return MODEL_RESERVED_KEYS.has(k) ? `${k}Field` : k;
395
+ return isReservedKey(k) ? `${k}Field` : k;
372
396
  }
397
+ /** The config keys a model's primitive list holds (types' PRIMITIVE_ARRAY_RULE). */
398
+ var MODEL_LIST_KEYS = new Set(PRIMITIVE_ARRAY_RULE.configKeys);
373
399
  /** A slot's V2 allow list as the backend keeps it: 1–50 distinct block names of 1–64 characters (slotAllowIssues). */
374
400
  var slotAllowOk = (allow) => Array.isArray(allow) && allow.length >= 1 && allow.length <= SLOT_ALLOW_MAX && new Set(allow).size === allow.length && allow.every((n) => typeof n === "string" && n === n.trim() && n.length >= 1 && n.length <= SLOT_ALLOW_NAME_MAX);
375
401
  function shapeOfField(f, lv, ctx, top) {
376
402
  const props = ctx.lane === "props";
377
- const deep = lv >= 10;
403
+ const deep = lv >= MAX_FIELD_DEPTH;
378
404
  const of = Array.isArray(f.of) ? f.of : void 0;
379
405
  /** a prop reference names a content model or a page / form / collection target (refTargetNamed): with none it is stored as json */
380
406
  const noEntry = (r) => props && !(r?.to ?? []).some((t) => ENTRY_TARGET(t) || REF_TARGET_KINDS.includes(t));
@@ -498,6 +524,36 @@ var trunc = (ctx, at, s, max, feature) => {
498
524
  lose(ctx, at, feature);
499
525
  return s.slice(0, max);
500
526
  };
527
+ /** The prod field types that store a rich text and its features (config.features). */
528
+ var RICH_TYPES = /* @__PURE__ */ new Set(["richtext", "document"]);
529
+ /** Every feature, canonical: RICHTEXT_FEATURES through normaliseRichTextFeatures, so no alias is ever in it. */
530
+ var ALL_RICH_FEATURES = normaliseRichTextFeatures(RICHTEXT_FEATURES).features;
531
+ /**
532
+ * A rich text's features through types normaliseRichTextFeatures (its aliases expand there: `headings` is h1–h6), distinct,
533
+ * in the order given. `named`: something was not carried (a name outside RICHTEXT_FEATURES, a repeat, a non-list).
534
+ */
535
+ /** A content model's id, as the backend's config.references rule reads one. */
536
+ var MODEL_ID = /^[A-Za-z0-9_-]{1,64}$/;
537
+ /** A rich text's references: posts and authors, each a model id; `named` when anything else was there. */
538
+ function richRefs(v) {
539
+ if (!v || typeof v !== "object" || Array.isArray(v)) return { named: true };
540
+ const r = v;
541
+ const kept = {};
542
+ for (const k of ["posts", "authors"]) if (typeof r[k] === "string" && MODEL_ID.test(r[k])) kept[k] = r[k];
543
+ const named = Object.keys(r).some((k) => k !== "posts" && k !== "authors" || r[k] !== void 0 && kept[k] === void 0);
544
+ return {
545
+ kept: Object.keys(kept).length ? kept : void 0,
546
+ named
547
+ };
548
+ }
549
+ function richFeatures(v) {
550
+ const names = Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
551
+ const { features, unknown } = normaliseRichTextFeatures(names);
552
+ return {
553
+ kept: [...new Set(features)],
554
+ named: !Array.isArray(v) || names.length !== v.length || new Set(names).size !== names.length || unknown.length > 0
555
+ };
556
+ }
501
557
  function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = false, inSetting = false) {
502
558
  const props = ctx.lane === "props";
503
559
  const shape = shapeOf(f, lv, ctx, top, inTable ? "rows" : inItem ? "item" : inSetting ? "setting" : void 0);
@@ -512,8 +568,8 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
512
568
  if (twins) {
513
569
  node.key = `${f.name}_${twins + 1}`;
514
570
  lose(ctx, `${at}/name`, "duplicate name");
515
- } else if (!props && modelKeyOf(f.name) !== f.name) {
516
- node.key = modelKeyOf(f.name);
571
+ } else if (!props && modelKeyOf(f.name, lv > 1) !== f.name) {
572
+ node.key = modelKeyOf(f.name, lv > 1);
517
573
  lose(ctx, `${at}/name`, "reserved name");
518
574
  }
519
575
  const nesting = shape.kind === "object" || shape.kind === "zones" || shape.kind === "types";
@@ -554,15 +610,15 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
554
610
  case "title":
555
611
  case "type": break;
556
612
  case "description":
557
- node.helpText = trunc(ctx, p, f.description, props ? 200 : 500, "help>limit");
613
+ node.helpText = trunc(ctx, p, f.description, props ? PROP_HELP_MAX : FIELD_HELP_MAX, "help>limit");
558
614
  break;
559
615
  case "helpMore":
560
616
  node.helpMore = trunc(ctx, p, f.helpMore, HELP_MORE_MAX, "help>limit");
561
617
  break;
562
618
  case "placeholder":
563
- if (props) node.placeholder = trunc(ctx, p, f.placeholder, 120, "help>limit");
619
+ if (props) node.placeholder = trunc(ctx, p, f.placeholder, PROP_PLACEHOLDER_MAX, "help>limit");
564
620
  else if (looseConfig) node.placeholder = f.placeholder;
565
- else node.ui.placeholder = trunc(ctx, p, f.placeholder, 120, "help>limit");
621
+ else node.ui.placeholder = trunc(ctx, p, f.placeholder, UI_PLACEHOLDER_MAX, "help>limit");
566
622
  break;
567
623
  case "initialValue":
568
624
  if (props && (shape.type === "reference" || shape.type === "multi-reference" || shape.kind === "slot")) cat(ctx, p, "seeded instance");
@@ -597,7 +653,7 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
597
653
  case "hidden":
598
654
  if (typeof f.hidden === "boolean") node.ui.hidden = f.hidden;
599
655
  else {
600
- const showIf = conditionToShowIf(f.hidden, p, siblings, ctx, props && top);
656
+ const showIf = conditionToShowIf(f.hidden, p, siblings, ctx, top && (props || !!ctx.variants));
601
657
  if (showIf) node.showIf = showIf;
602
658
  }
603
659
  break;
@@ -630,7 +686,7 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
630
686
  node.advanced = f.advanced;
631
687
  break;
632
688
  case "ai":
633
- cat(ctx, p, "ai");
689
+ node.ai = f.ai;
634
690
  break;
635
691
  case "reference":
636
692
  if (shape.kind === "leaf" && shape.type === "reference") referenceToConfig(f.reference, node, p, ctx);
@@ -703,10 +759,27 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
703
759
  if (shape.kind === "leaf" && shape.type === "table") Object.assign(node.config, tableConfigFromOptions(f.table));
704
760
  else lose(ctx, p, "type:table");
705
761
  break;
762
+ case "references":
763
+ if (!RICH_TYPES.has(shape.type)) lose(ctx, p, "unsupported key");
764
+ else {
765
+ const { kept, named } = richRefs(f.references);
766
+ if (named) lose(ctx, p, "richtext references");
767
+ if (kept) node.config.references = kept;
768
+ }
769
+ break;
770
+ case "features":
771
+ if (!RICH_TYPES.has(shape.type)) lose(ctx, p, "unsupported key");
772
+ else {
773
+ const { kept, named } = richFeatures(f.features);
774
+ if (named) lose(ctx, p, "richtext features");
775
+ node.config.features = kept;
776
+ }
777
+ break;
706
778
  default: lose(ctx, p, "unsupported key");
707
779
  }
708
780
  }
709
781
  if (props && f.description === void 0) node.helpText = null;
782
+ if (RICH_TYPES.has(shape.type) && shape.kind === "leaf" && f.features === void 0) node.config.features = [...ALL_RICH_FEATURES];
710
783
  if (f.itemNoun === void 0 && shape.kind === "zones" && node.ui.itemLabel !== void 0) cat(ctx, `${at}/itemNoun`, "itemNoun (derived)");
711
784
  placeInSet(f, node, at, ctx, top, siblings);
712
785
  if (f.type === "rating" && node.type === "rating" && f.numeric?.step === void 0) {
@@ -760,10 +833,24 @@ function placeInSet(f, node, at, ctx, top, siblings) {
760
833
  }
761
834
  return;
762
835
  }
836
+ if (f.fieldset !== void 0 && !ctx.sets.has(f.fieldset) && !ctx.tabs.has(f.fieldset) && !ctx.droppedTabs.has(f.fieldset) && f.fieldset.length <= 60) {
837
+ const derived = ctx.derivedSets ??= [];
838
+ if (derived.length + ctx.sets.size + ctx.tabs.size < 30) {
839
+ derived.push({
840
+ id: f.fieldset,
841
+ name: f.fieldset,
842
+ kind: "group"
843
+ });
844
+ ctx.sets.set(f.fieldset, void 0);
845
+ }
846
+ }
763
847
  const set = f.fieldset !== void 0 && ctx.sets.has(f.fieldset) ? f.fieldset : void 0;
764
848
  if (f.fieldset !== void 0 && !set) lose(ctx, `${at}/fieldset`, "fieldset collision");
765
849
  if (!top) {
766
- if (f.group !== void 0) lose(ctx, `${at}/group`, "group (nested)");
850
+ if (f.group !== void 0) {
851
+ if (ctx.tabs.has(f.group)) node.ui.tab = { id: f.group };
852
+ else lose(ctx, `${at}/group`, "group (nested)");
853
+ }
767
854
  if (set) node.fieldsetId = set;
768
855
  return;
769
856
  }
@@ -787,7 +874,7 @@ function propGroupOf(s, ctx) {
787
874
  if (ctx.groupOrder) g.order = Math.min(99, ctx.propOrder.get(s.name) ?? 0);
788
875
  return g;
789
876
  }
790
- var groupDescriptionOk = (d) => typeof d === "string" && d === d.trim() && d.length >= 1 && d.length <= 200;
877
+ var groupDescriptionOk = (d) => typeof d === "string" && d === d.trim() && d.length >= 1 && d.length <= PROP_GROUP_DESCRIPTION_MAX;
791
878
  /** The zone a datetime field fixes, if any (what lets a stored instant read back with its [Zone]). */
792
879
  var fixedZone = (f) => typeof f.timezone === "string" && f.timezone.startsWith("fixed:") ? f.timezone.slice(6) : void 0;
793
880
  /**
@@ -1226,7 +1313,7 @@ function optionsToProd(f, shape, node, at, ctx) {
1226
1313
  }
1227
1314
  const d = o.description;
1228
1315
  if (d !== void 0) {
1229
- if (typeof d === "string" && d.trim() !== "" && !/[\r\n]/.test(d) && d.length <= 140) out.description = d;
1316
+ if (typeof d === "string" && d.trim() !== "" && !/[\r\n]/.test(d) && d.length <= OPTION_DESCRIPTION_MAX) out.description = d;
1230
1317
  else lose(ctx, `${at}/${i}/description`, "option description");
1231
1318
  }
1232
1319
  for (const k of Object.keys(o)) if (![
@@ -1244,8 +1331,8 @@ function layoutToProd(f, shape, node, at, ctx) {
1244
1331
  const t = shape.type;
1245
1332
  const n = f.options?.length ?? 0;
1246
1333
  if (l === "segmented" && t === "select") {
1247
- if (n >= 2 && n <= 4) node.ui.control = "segmented";
1248
- else if (n >= 5) cat(ctx, at, "segmented fallback");
1334
+ if (n >= SEGMENTED_MIN && n <= SEGMENTED_MAX) node.ui.control = "segmented";
1335
+ else if (n > SEGMENTED_MAX) cat(ctx, at, "segmented fallback");
1249
1336
  else lose(ctx, at, "layout");
1250
1337
  } else if (l === "slider" && t === "number" && f.type === "number") node.ui.control = "slider";
1251
1338
  else if (UI_CONTROLS.includes(l) && l !== "segmented" && l !== "slider" && CONTROL_TYPES[l]?.includes(t)) node.ui.control = l;
@@ -1435,7 +1522,7 @@ function ofToProd(f, shape, node, at, lv, ctx) {
1435
1522
  if (shape.kind === "itemType") node.config.itemType = shape.itemType;
1436
1523
  itemIdentity(item, conventionItem(f.name, f.title), node, ip, ctx);
1437
1524
  const settings = {};
1438
- const scalars = shape.kind === "itemType" && !shape.shopify && ctx.lane === "props";
1525
+ const scalars = shape.kind === "itemType" && !shape.shopify && (ctx.lane === "props" || MODEL_LIST_KEYS.has("item"));
1439
1526
  for (const k of Object.keys(item)) {
1440
1527
  const v = item[k];
1441
1528
  if (v === void 0) continue;
@@ -1446,7 +1533,7 @@ function ofToProd(f, shape, node, at, lv, ctx) {
1446
1533
  if (shape.kind === "itemType" && item.type === "text") lose(ctx, p, "item settings");
1447
1534
  } else if (k === "reference" && shape.kind === "refs") referenceToConfig(item.reference, node, p, ctx);
1448
1535
  else if (k === "reference" && shape.kind === "itemType" && shape.shopify) continue;
1449
- else if (scalars && k === "description" && typeof v === "string" && v.trim() === v && v.length >= 1 && v.length <= 200) settings.helpText = v;
1536
+ else if (scalars && k === "description" && typeof v === "string" && v.trim() === v && v.length >= 1 && v.length <= PROP_HELP_MAX) settings.helpText = v;
1450
1537
  else if (scalars && k === "localized" && typeof v === "boolean") settings.localized = v;
1451
1538
  else if (scalars && k === "validation") itemValidationToProd(item, shape.itemType, settings, p, ctx);
1452
1539
  else lose(ctx, p, "item settings");
@@ -1548,28 +1635,8 @@ function blockModelFor(item, at, ctx, list) {
1548
1635
  if (item[k] === void 0 || k === "type" || k === "title") continue;
1549
1636
  if (k === "name") {
1550
1637
  if (camel(slug) !== item.name) lose(ctx, p, "item identity");
1551
- } else if (k === "fields") {
1552
- let dropped = false;
1553
- let shifted = false;
1554
- fields.forEach((c, j) => {
1555
- const fp = `${p}/${j}`;
1556
- const mark = ctx.lossy.length;
1557
- const markCat = ctx.catalog.length;
1558
- const node = convertField(c, fp, 1, sub, fields, false);
1559
- if (BLOCK_FIELD_TYPES.has(node.type)) {
1560
- kept.push(emitField(node));
1561
- if (dropped && !shifted) {
1562
- shifted = true;
1563
- lose(ctx, p, "block model field");
1564
- }
1565
- return;
1566
- }
1567
- ctx.lossy.length = mark;
1568
- ctx.catalog.length = markCat;
1569
- dropped = true;
1570
- lose(ctx, fp, "block model field");
1571
- });
1572
- } else if (k === "openMode") cat(ctx, p, "item openMode");
1638
+ } else if (k === "fields") fields.forEach((c, j) => kept.push(emitField(convertField(c, `${p}/${j}`, 1, sub, fields, false))));
1639
+ else if (k === "openMode") chrome.openMode = item.openMode;
1573
1640
  else if (k === "icon") {
1574
1641
  const dock = dockIconOf(item.icon);
1575
1642
  if (dock) chrome.icon = dock;
@@ -1577,11 +1644,17 @@ function blockModelFor(item, at, ctx, list) {
1577
1644
  } else if (k === "preview") chrome.preview = previewToProd(item.preview, fields, p, ctx);
1578
1645
  else lose(ctx, p, "item identity");
1579
1646
  }
1580
- const model = {
1647
+ const model = kept.every(blockRenders) ? {
1581
1648
  name: item.title,
1582
1649
  slug,
1583
1650
  kind: "block",
1584
1651
  fields: kept
1652
+ } : {
1653
+ name: item.title,
1654
+ slug,
1655
+ kind: "model",
1656
+ fields: kept,
1657
+ v2: { shape: "block" }
1585
1658
  };
1586
1659
  const prev = ctx.blockModels.get(slug);
1587
1660
  const keep = () => {
@@ -1751,13 +1824,14 @@ function conditionToShowIf(h, at, siblings, ctx, variantOk) {
1751
1824
  /** One V2 rule as a prod rule, or null when prod cannot hold it (a scope, a non-sibling, a value showIf cannot test). */
1752
1825
  function conditionRule(r, head, siblings, variantOk) {
1753
1826
  const variant = variantOk && r.field === "_variant";
1754
- if (!r.field || r.scope !== void 0 || !variant && !siblings.some((s) => s.name === r.field)) return null;
1827
+ if (!r.field || r.scope === void 0 && !variant && !siblings.some((s) => s.name === r.field)) return null;
1755
1828
  const allowed = head ? [
1756
1829
  "field",
1757
1830
  "equals",
1758
1831
  "notEmpty",
1759
1832
  "op",
1760
1833
  "value",
1834
+ "scope",
1761
1835
  "also",
1762
1836
  "match"
1763
1837
  ] : [
@@ -1765,7 +1839,8 @@ function conditionRule(r, head, siblings, variantOk) {
1765
1839
  "equals",
1766
1840
  "notEmpty",
1767
1841
  "op",
1768
- "value"
1842
+ "value",
1843
+ "scope"
1769
1844
  ];
1770
1845
  if (Object.keys(r).some((k) => !allowed.includes(k) && r[k] !== void 0)) return null;
1771
1846
  if ([
@@ -1773,22 +1848,26 @@ function conditionRule(r, head, siblings, variantOk) {
1773
1848
  r.equals !== void 0,
1774
1849
  r.notEmpty !== void 0
1775
1850
  ].filter(Boolean).length !== 1) return null;
1851
+ const scope = r.scope ? { scope: r.scope } : {};
1776
1852
  if (r.notEmpty !== void 0) return r.notEmpty === true ? {
1777
1853
  field: r.field,
1778
- op: "notEmpty"
1854
+ op: "notEmpty",
1855
+ ...scope
1779
1856
  } : null;
1780
1857
  const target = r.op !== void 0 ? r.value : r.equals;
1781
1858
  const op = r.op !== void 0 ? SHOW_IF_OP[r.op] : Array.isArray(target) ? "in" : "eq";
1782
1859
  if (!op) return null;
1783
1860
  if (VALUELESS.has(op)) return r.value === void 0 ? {
1784
1861
  field: r.field,
1785
- op
1862
+ op,
1863
+ ...scope
1786
1864
  } : null;
1787
- const sibType = variant ? void 0 : siblings.find((s) => s.name === r.field)?.type;
1865
+ const sibType = variant || r.scope ? void 0 : siblings.find((s) => s.name === r.field)?.type;
1788
1866
  if (op === "gt" || op === "lt") return typeof target === "number" && Number.isFinite(target) ? {
1789
1867
  field: r.field,
1790
1868
  op,
1791
- value: String(target)
1869
+ value: String(target),
1870
+ ...scope
1792
1871
  } : null;
1793
1872
  const list = Array.isArray(target) ? target : [target];
1794
1873
  const strs = [];
@@ -1805,16 +1884,19 @@ function conditionRule(r, head, siblings, variantOk) {
1805
1884
  if (op === "in" || op === "notIn") return {
1806
1885
  field: r.field,
1807
1886
  op,
1808
- value: strs
1887
+ value: strs,
1888
+ ...scope
1809
1889
  };
1810
1890
  return Array.isArray(target) ? strs.length ? {
1811
1891
  field: r.field,
1812
1892
  op: op === "eq" ? "in" : "notIn",
1813
- value: strs
1893
+ value: strs,
1894
+ ...scope
1814
1895
  } : null : {
1815
1896
  field: r.field,
1816
1897
  op,
1817
- value: strs[0]
1898
+ value: strs[0],
1899
+ ...scope
1818
1900
  };
1819
1901
  }
1820
1902
  var nonEmpty = (o) => Object.keys(o).length ? o : void 0;
@@ -1831,6 +1913,7 @@ function common(n) {
1831
1913
  "showIf",
1832
1914
  "helpMore",
1833
1915
  "advanced",
1916
+ "ai",
1834
1917
  "recommendedMax",
1835
1918
  "openMode",
1836
1919
  "localized",
@@ -1924,9 +2007,12 @@ function fromTypeSchema(v2, target) {
1924
2007
  blockModels: /* @__PURE__ */ new Map()
1925
2008
  };
1926
2009
  const props = target === "props";
2010
+ if (!props && v2.variants?.length) ctx.variants = true;
1927
2011
  const slug = kebab(v2.name);
1928
- const category = v2.category !== void 0 ? CATEGORY_TO_PROD[v2.category] : void 0;
2012
+ const kitCategory = KIT_CATEGORIES.includes(v2.category ?? "") ? v2.category : void 0;
2013
+ const category = kitCategory ?? (v2.category !== void 0 ? CATEGORY_TO_PROD[v2.category] : void 0);
1929
2014
  const fieldsets = [];
2015
+ const block = {};
1930
2016
  for (const k of Object.keys(v2)) {
1931
2017
  if (v2[k] === void 0) continue;
1932
2018
  const p = `/${k}`;
@@ -1940,23 +2026,22 @@ function fromTypeSchema(v2, target) {
1940
2026
  case "fieldsets":
1941
2027
  case "groups": break;
1942
2028
  case "kind":
1943
- if (props && v2.kind !== "block") cat(ctx, p, v2.kind === "global" ? "kind:global" : "kind:entry (props)");
1944
- else if (!props && v2.kind !== "entry") cat(ctx, p, v2.kind === "global" ? "kind:global" : v2.kind === "page" ? "kind:page" : v2.kind === "block" ? "kind:block (fields)" : "kind:object");
2029
+ if (props ? v2.kind !== "block" : v2.kind !== "entry") block.shape = v2.kind;
1945
2030
  break;
1946
2031
  case "icon":
1947
- if (!props || !dockIconOf(v2.icon)) cat(ctx, p, "block icon");
1948
- break;
1949
- case "preview":
1950
- if (!props) cat(ctx, p, "document preview");
2032
+ if (!dockIconOf(v2.icon)) cat(ctx, p, "block icon");
1951
2033
  break;
2034
+ case "preview": break;
1952
2035
  case "variants":
1953
- cat(ctx, p, "variants");
2036
+ if (!ctx.variants) cat(ctx, p, "variants");
1954
2037
  break;
1955
2038
  case "category":
1956
- if (!props || !category || capitalize(category) !== v2.category) cat(ctx, p, "category");
2039
+ if (kitCategory) {
2040
+ if (!props) block.category = kitCategory;
2041
+ } else if (!props || !category || capitalize(category) !== v2.category) cat(ctx, p, "category");
1957
2042
  break;
1958
2043
  case "placement":
1959
- cat(ctx, p, "block placement");
2044
+ block.placement = v2.placement;
1960
2045
  break;
1961
2046
  case "initialValue":
1962
2047
  cat(ctx, p, "seeded instance");
@@ -1977,6 +2062,7 @@ function fromTypeSchema(v2, target) {
1977
2062
  };
1978
2063
  if (v2.description !== void 0) component.description = v2.description;
1979
2064
  if (category) component.category = category;
2065
+ if (nonEmpty(block)) component.v2 = { ...block };
1980
2066
  const icon = v2.icon === void 0 ? void 0 : dockIconOf(v2.icon);
1981
2067
  if (icon) component.icon = icon;
1982
2068
  if (v2.preview !== void 0) {
@@ -1997,7 +2083,35 @@ function fromTypeSchema(v2, target) {
1997
2083
  fields: nodes.map(emitField)
1998
2084
  };
1999
2085
  if (v2.description !== void 0) model.description = v2.description;
2000
- model.kind = "model";
2086
+ const modelIcon = v2.icon === void 0 ? void 0 : dockIconOf(v2.icon);
2087
+ if (modelIcon) model.icon = modelIcon;
2088
+ model.kind = block.shape === "block" && model.fields.every(blockRenders) ? "block" : "model";
2089
+ if (ctx.variants) {
2090
+ const variants = v2.variants;
2091
+ model.fields.unshift({
2092
+ key: BLOCK_VARIANT_KEY,
2093
+ label: "Variant",
2094
+ type: "select",
2095
+ required: false,
2096
+ options: variants.map((v) => ({
2097
+ label: v.name,
2098
+ value: v.id
2099
+ }))
2100
+ });
2101
+ model.v2 = {
2102
+ ...block,
2103
+ variants: Object.fromEntries(variants.map((v) => [v.id, v.description !== void 0 ? { description: v.description } : {}]))
2104
+ };
2105
+ } else if (nonEmpty(block)) model.v2 = { ...block };
2106
+ if (v2.preview !== void 0) {
2107
+ const preview = componentPreviewToProd(v2.preview, new Set(model.fields.map((x) => x.key)), v2.fields, "/preview", ctx);
2108
+ if (Object.keys(preview).length) model.listPreview = preview;
2109
+ }
2110
+ if (ctx.derivedSets) {
2111
+ const declared = fieldsets.filter((x) => x.kind !== "tab").length;
2112
+ ctx.derivedSets.forEach((_, i) => cat(ctx, `/fieldsets/${declared + i}`, "fieldset (derived)"));
2113
+ fieldsets.push(...ctx.derivedSets);
2114
+ }
2001
2115
  if (fieldsets.length) model.fieldsets = fieldsets;
2002
2116
  return {
2003
2117
  schema: model,
@@ -2021,7 +2135,7 @@ function fieldsetsFor(v2, ctx, out) {
2021
2135
  lose(ctx, p, why);
2022
2136
  };
2023
2137
  if (ctx.tabs.has(g.name) || names.has(folded)) return drop("group name collision");
2024
- if (out.length >= 3) return drop("groups>3");
2138
+ if (out.length >= TAB_MAX$1) return drop("groups>3");
2025
2139
  names.add(folded);
2026
2140
  const tab = {
2027
2141
  id: g.name,
@@ -2066,7 +2180,7 @@ function fieldsetsFor(v2, ctx, out) {
2066
2180
  const v = fs[k];
2067
2181
  if (v === void 0 || k === "name" || k === "title") continue;
2068
2182
  if (k === "description") {
2069
- if (v.length <= 500) set.description = v;
2183
+ if (v.length <= FIELDSET_DESCRIPTION_MAX) set.description = v;
2070
2184
  else lose(ctx, `${p}/${k}`, "help>limit");
2071
2185
  } else if (k === "collapsed" || k === "collapsible" || k === "columns" || k === "layout") set[k] = v;
2072
2186
  else lose(ctx, `${p}/${k}`, "unsupported key");
@@ -2095,7 +2209,7 @@ function propTabsFor(v2, ctx) {
2095
2209
  const label = g.title.slice(0, 40).trim();
2096
2210
  const folded = label.toLowerCase();
2097
2211
  if (kept.some((k) => k.g.name === g.name) || labels.has(folded)) return drop("group name collision");
2098
- if (kept.length >= 8 || g.name.length < 1 || g.name.length > 40) return drop("prop tab limit");
2212
+ if (kept.length >= PROP_TAB_MAX || g.name.length < 1 || g.name.length > 40) return drop("prop tab limit");
2099
2213
  if (!label) return drop("group title");
2100
2214
  labels.add(folded);
2101
2215
  const tab = {
@@ -2198,7 +2312,7 @@ function propSetsFor(v2, ctx) {
2198
2312
  firstSeen.push(f.fieldset);
2199
2313
  if (!named.has(f.fieldset)) undeclared.push(f.fieldset);
2200
2314
  }
2201
- if (lv >= 10) continue;
2315
+ if (lv >= MAX_FIELD_DEPTH) continue;
2202
2316
  const kids = previewKids(f);
2203
2317
  if (kids) visit(kids, lv + 1);
2204
2318
  }
@@ -2381,9 +2495,12 @@ function previewFromProd(pv, children) {
2381
2495
  function showIfToCondition(s, siblings, at, ctx) {
2382
2496
  const conds = [];
2383
2497
  for (const r of s.rules) {
2384
- const c = showIfRuleToCondition(r, siblings);
2498
+ const c = showIfRuleToCondition(r, r.scope ? [] : siblings);
2385
2499
  if (!c) return void ctx.onLoss(at, "condition", `V2 conditions have no "${r.op}" with this value; the field always shows.`);
2386
- conds.push(c);
2500
+ conds.push(r.scope ? {
2501
+ ...c,
2502
+ scope: r.scope
2503
+ } : c);
2387
2504
  }
2388
2505
  if (!conds.length) return void 0;
2389
2506
  const [head, ...also] = conds;
@@ -2497,6 +2614,7 @@ function readToV2(n, at, siblings, ctx, top, lane) {
2497
2614
  if (n.unique !== void 0) val.unique = n.unique;
2498
2615
  if (n.recommendedMax !== void 0) val.recommendedMax = n.recommendedMax;
2499
2616
  if (n.advanced !== void 0) f.advanced = n.advanced;
2617
+ if (n.ai !== void 0) f.ai = n.ai;
2500
2618
  if (n.openMode !== void 0) f.openMode = n.openMode;
2501
2619
  if (n.readOnly === true) f.readOnly = true;
2502
2620
  if (n.archived === true) f.archived = true;
@@ -2536,7 +2654,7 @@ function readToV2(n, at, siblings, ctx, top, lane) {
2536
2654
  } else loss("fieldsetId", "fieldset", `No fieldset "${n.fieldsetId}" exists on this model; the membership is dropped.`);
2537
2655
  }
2538
2656
  if (lane === "props" && n.ui.group) f.fieldset = n.ui.group.id;
2539
- if (lane === "props" && n.ui.tab) f.group = n.ui.tab.id;
2657
+ if ((lane === "props" || !top) && n.ui.tab) f.group = n.ui.tab.id;
2540
2658
  for (const k of [
2541
2659
  "target",
2542
2660
  "stale",
@@ -2560,8 +2678,21 @@ function readToV2(n, at, siblings, ctx, top, lane) {
2560
2678
  ]);
2561
2679
  const use = (...k) => k.forEach((x) => used.add(x));
2562
2680
  const useUi = (...k) => k.forEach((x) => usedUi.add(x));
2681
+ if (!top && lane === "fields") useUi("tab");
2563
2682
  if (lane === "props") useUi("group");
2564
2683
  if (lane === "props") useUi("tab");
2684
+ if (RICH_TYPES.has(n.type) && cfg.references !== void 0) {
2685
+ use("references");
2686
+ const { kept, named } = richRefs(cfg.references);
2687
+ if (named) loss("config/references", "richtext references", FEATURES["richtext references"].reason);
2688
+ if (kept) f.references = kept;
2689
+ }
2690
+ if (RICH_TYPES.has(n.type) && cfg.features !== void 0) {
2691
+ use("features");
2692
+ const { kept, named } = richFeatures(cfg.features);
2693
+ if (named) loss("config/features", "richtext features", FEATURES["richtext features"].reason);
2694
+ if (kept.length < ALL_RICH_FEATURES.length) f.features = kept;
2695
+ }
2565
2696
  if ([
2566
2697
  "text",
2567
2698
  "longtext",
@@ -2770,6 +2901,7 @@ function readToV2(n, at, siblings, ctx, top, lane) {
2770
2901
  };
2771
2902
  if (chrome?.icon) item.icon = tablerIconOf(chrome.icon);
2772
2903
  if (chrome?.preview) item.preview = previewFromProd(chrome.preview, kids);
2904
+ if (chrome?.openMode) item.openMode = chrome.openMode;
2773
2905
  return item;
2774
2906
  });
2775
2907
  const missing = slugs.filter((s) => !ctx.blockModels.has(s));
@@ -2927,14 +3059,27 @@ function toTypeSchema(model, opts = {}) {
2927
3059
  if (s.icon !== void 0) ctx.onLoss(`/fieldsets/${i}/icon`, "fieldset icon", "V2 sections have no icon.");
2928
3060
  fieldsets.push(fs);
2929
3061
  });
2930
- const nodes = model.fields.map(readField);
3062
+ const variantField = model.v2?.variants ? model.fields.find((f) => f.key === BLOCK_VARIANT_KEY && f.type === "select") : void 0;
3063
+ const nodes = model.fields.filter((f) => f !== variantField).map(readField);
2931
3064
  const out = {
2932
3065
  name: camel(model.slug),
2933
3066
  title: model.name,
2934
- kind: model.kind === "block" ? "block" : "entry",
3067
+ kind: model.kind === "block" ? "block" : model.v2?.shape ?? "entry",
2935
3068
  fields: nodes.map((n, i) => readToV2(n, `/fields/${i}`, nodes, ctx, true, "fields"))
2936
3069
  };
3070
+ if (model.v2?.placement) out.placement = model.v2.placement;
3071
+ if (variantField) {
3072
+ const meta = model.v2.variants;
3073
+ out.variants = (variantField.options ?? []).map((o) => ({
3074
+ id: o.value,
3075
+ name: o.label,
3076
+ ...meta[o.value]?.description !== void 0 ? { description: meta[o.value].description } : {}
3077
+ }));
3078
+ }
3079
+ if (model.listPreview) out.preview = previewFromProd(model.listPreview, nodes);
3080
+ if (model.v2?.category) out.category = model.v2.category;
2937
3081
  if (model.description !== void 0) out.description = model.description;
3082
+ if (model.icon) out.icon = tablerIconOf(model.icon);
2938
3083
  if (fieldsets.length) out.fieldsets = fieldsets;
2939
3084
  if (groups.length) out.groups = groups;
2940
3085
  return out;
@@ -3003,18 +3148,72 @@ function propsToTypeSchema(component, opts = {}) {
3003
3148
  const out = {
3004
3149
  name: camel(component.slug),
3005
3150
  title: component.name,
3006
- kind: "block",
3151
+ kind: component.v2?.shape ?? "block",
3007
3152
  fields: nodes.map((n, i) => readToV2(n, `/fields/${i}`, nodes, ctx, true, "props"))
3008
3153
  };
3154
+ if (component.v2?.placement) out.placement = component.v2.placement;
3009
3155
  if (component.description !== void 0 && component.description !== null) out.description = component.description;
3010
- if (component.category !== void 0 && component.category !== "custom") out.category = capitalize(component.category);
3156
+ if (component.category !== void 0 && component.category !== "custom") out.category = KIT_CATEGORIES.includes(component.category) ? component.category : capitalize(component.category);
3011
3157
  if (component.icon) out.icon = tablerIconOf(component.icon);
3012
3158
  if (component.preview) out.preview = previewFromProd(component.preview, nodes);
3013
3159
  if (fieldsets.length) out.fieldsets = fieldsets;
3014
3160
  if (groups.length) out.groups = groups;
3015
3161
  return out;
3016
3162
  }
3163
+ /** A V2 variant id a member slug can carry, so the read takes it back verbatim (the component slug rule). */
3164
+ var VARIANT_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3165
+ function variantFamilyFromTypeSchema(v2) {
3166
+ const base = fromTypeSchema(v2, "props");
3167
+ const variants = v2.variants ?? [];
3168
+ if (!variants.length || !variants.every((v) => VARIANT_ID_RE.test(v.id))) return {
3169
+ members: [base.schema],
3170
+ group: null,
3171
+ lossy: base.lossy,
3172
+ catalog: base.catalog
3173
+ };
3174
+ const slug = base.schema.slug;
3175
+ return {
3176
+ members: variants.map((v) => ({
3177
+ ...base.schema,
3178
+ name: v.name,
3179
+ slug: `${slug}-${v.id}`,
3180
+ sectionType: v2.title
3181
+ })),
3182
+ group: {
3183
+ name: v2.title,
3184
+ slug,
3185
+ variants: variants.map((v) => ({
3186
+ member: `${slug}-${v.id}`,
3187
+ ...v.description !== void 0 ? { description: v.description } : {}
3188
+ }))
3189
+ },
3190
+ lossy: base.lossy,
3191
+ catalog: base.catalog.filter((c) => c.feature !== "variants")
3192
+ };
3193
+ }
3194
+ /** The group's `v2` (component_variant_groups.v2, variantGroupV2Schema) once the members exist: `idOf` maps a member slug to its id. */
3195
+ function variantGroupV2(group, idOf) {
3196
+ return { variants: group.variants.map((v) => ({
3197
+ componentId: idOf(v.member),
3198
+ ...v.description !== void 0 ? { description: v.description } : {}
3199
+ })) };
3200
+ }
3201
+ /**
3202
+ * A family read back as one V2 block: the first member's props, the family's name and title, and its variants from the
3203
+ * group's members in `v2.variants` order (the id after the group slug, the member's name, the meta's description).
3204
+ */
3205
+ function variantFamilyToTypeSchema(members, group, opts = {}) {
3206
+ const out = propsToTypeSchema(members[0], opts);
3207
+ out.name = camel(group.slug);
3208
+ if (members[0].sectionType) out.title = members[0].sectionType;
3209
+ out.variants = members.map((m, i) => ({
3210
+ id: m.slug.slice(group.slug.length + 1),
3211
+ name: m.name,
3212
+ ...group.variants[i]?.description !== void 0 ? { description: group.variants[i].description } : {}
3213
+ }));
3214
+ return out;
3215
+ }
3017
3216
  //#endregion
3018
- export { CATEGORY_TO_PROD, SHOPIFY_REF_LOCK, fromTypeSchema, leafValuePlan, planFromProd, planToProd, propsToTypeSchema, schemaSlugOf, toTypeSchema };
3217
+ export { CATEGORY_TO_PROD, SHOPIFY_REF_LOCK, fromTypeSchema, leafValuePlan, planFromProd, planToProd, propsToTypeSchema, schemaSlugOf, toTypeSchema, variantFamilyFromTypeSchema, variantFamilyToTypeSchema, variantGroupV2 };
3019
3218
 
3020
3219
  //# sourceMappingURL=schema.js.map