@bettercms-ai/kit 2.0.0-next.6 → 2.0.0-next.8

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.
@@ -4,7 +4,7 @@ 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, PRIMITIVE_ARRAY_RULE, RICHTEXT_FEATURES, isReservedKey, normaliseRichTextFeatures } from "@bettercms-ai/types";
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";
8
8
  //#region src/adapters/schema.ts
9
9
  var isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
10
10
  function deepEqual(a, b) {
@@ -185,6 +185,44 @@ var CATEGORY_TO_PROD = {
185
185
  };
186
186
  /** The field types a kind:'block' model holds today: the ones a block primitive renders (types, staged to G7). */
187
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([
192
+ "text",
193
+ "longtext",
194
+ "richtext",
195
+ "image",
196
+ "link",
197
+ "slug",
198
+ "email",
199
+ "phone",
200
+ "color",
201
+ "select",
202
+ "number",
203
+ "rating",
204
+ "date",
205
+ "datetime",
206
+ "boolean"
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
+ }
188
226
  /** A slot prop's config.minItems / maxItems cap (SLOT_ITEMS_CAP, componentPropDefSchema). */
189
227
  var SLOT_ITEMS_CAP = 50;
190
228
  /** A table prop's rows and an array prop's items: their config.minItems / maxItems cap (LIST_ITEMS_CAP). */
@@ -348,8 +386,10 @@ function shapeOf(f, lv, ctx, top, nest) {
348
386
  * `layout`, `page`, a forbidden key or a leading `_`; or one off KEY_FORMAT), renamed. V2 authoring follows the API
349
387
  * lane, so `id` is a field key like any other (only the derive lane, which mints keys, reserves it).
350
388
  */
351
- function modelKeyOf(name) {
352
- if (KEY_FORMAT.test(name) && !isReservedKey(name)) return name;
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;
353
393
  let k = name.replace(/^_+/, "").replace(/[^A-Za-z0-9_-]/g, "");
354
394
  if (!/^[A-Za-z]/.test(k)) k = `f${k}`;
355
395
  return isReservedKey(k) ? `${k}Field` : k;
@@ -528,8 +568,8 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
528
568
  if (twins) {
529
569
  node.key = `${f.name}_${twins + 1}`;
530
570
  lose(ctx, `${at}/name`, "duplicate name");
531
- } else if (!props && modelKeyOf(f.name) !== f.name) {
532
- node.key = modelKeyOf(f.name);
571
+ } else if (!props && modelKeyOf(f.name, lv > 1) !== f.name) {
572
+ node.key = modelKeyOf(f.name, lv > 1);
533
573
  lose(ctx, `${at}/name`, "reserved name");
534
574
  }
535
575
  const nesting = shape.kind === "object" || shape.kind === "zones" || shape.kind === "types";
@@ -613,7 +653,7 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
613
653
  case "hidden":
614
654
  if (typeof f.hidden === "boolean") node.ui.hidden = f.hidden;
615
655
  else {
616
- const showIf = conditionToShowIf(f.hidden, p, siblings, ctx, props && top);
656
+ const showIf = conditionToShowIf(f.hidden, p, siblings, ctx, top && (props || !!ctx.variants));
617
657
  if (showIf) node.showIf = showIf;
618
658
  }
619
659
  break;
@@ -646,7 +686,7 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
646
686
  node.advanced = f.advanced;
647
687
  break;
648
688
  case "ai":
649
- cat(ctx, p, "ai");
689
+ node.ai = f.ai;
650
690
  break;
651
691
  case "reference":
652
692
  if (shape.kind === "leaf" && shape.type === "reference") referenceToConfig(f.reference, node, p, ctx);
@@ -793,10 +833,24 @@ function placeInSet(f, node, at, ctx, top, siblings) {
793
833
  }
794
834
  return;
795
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
+ }
796
847
  const set = f.fieldset !== void 0 && ctx.sets.has(f.fieldset) ? f.fieldset : void 0;
797
848
  if (f.fieldset !== void 0 && !set) lose(ctx, `${at}/fieldset`, "fieldset collision");
798
849
  if (!top) {
799
- 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
+ }
800
854
  if (set) node.fieldsetId = set;
801
855
  return;
802
856
  }
@@ -1277,8 +1331,8 @@ function layoutToProd(f, shape, node, at, ctx) {
1277
1331
  const t = shape.type;
1278
1332
  const n = f.options?.length ?? 0;
1279
1333
  if (l === "segmented" && t === "select") {
1280
- if (n >= 2 && n <= 4) node.ui.control = "segmented";
1281
- 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");
1282
1336
  else lose(ctx, at, "layout");
1283
1337
  } else if (l === "slider" && t === "number" && f.type === "number") node.ui.control = "slider";
1284
1338
  else if (UI_CONTROLS.includes(l) && l !== "segmented" && l !== "slider" && CONTROL_TYPES[l]?.includes(t)) node.ui.control = l;
@@ -1581,28 +1635,8 @@ function blockModelFor(item, at, ctx, list) {
1581
1635
  if (item[k] === void 0 || k === "type" || k === "title") continue;
1582
1636
  if (k === "name") {
1583
1637
  if (camel(slug) !== item.name) lose(ctx, p, "item identity");
1584
- } else if (k === "fields") {
1585
- let dropped = false;
1586
- let shifted = false;
1587
- fields.forEach((c, j) => {
1588
- const fp = `${p}/${j}`;
1589
- const mark = ctx.lossy.length;
1590
- const markCat = ctx.catalog.length;
1591
- const node = convertField(c, fp, 1, sub, fields, false);
1592
- if (BLOCK_FIELD_TYPES.has(node.type)) {
1593
- kept.push(emitField(node));
1594
- if (dropped && !shifted) {
1595
- shifted = true;
1596
- lose(ctx, p, "block model field");
1597
- }
1598
- return;
1599
- }
1600
- ctx.lossy.length = mark;
1601
- ctx.catalog.length = markCat;
1602
- dropped = true;
1603
- lose(ctx, fp, "block model field");
1604
- });
1605
- } 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;
1606
1640
  else if (k === "icon") {
1607
1641
  const dock = dockIconOf(item.icon);
1608
1642
  if (dock) chrome.icon = dock;
@@ -1610,11 +1644,17 @@ function blockModelFor(item, at, ctx, list) {
1610
1644
  } else if (k === "preview") chrome.preview = previewToProd(item.preview, fields, p, ctx);
1611
1645
  else lose(ctx, p, "item identity");
1612
1646
  }
1613
- const model = {
1647
+ const model = kept.every(blockRenders) ? {
1614
1648
  name: item.title,
1615
1649
  slug,
1616
1650
  kind: "block",
1617
1651
  fields: kept
1652
+ } : {
1653
+ name: item.title,
1654
+ slug,
1655
+ kind: "model",
1656
+ fields: kept,
1657
+ v2: { shape: "block" }
1618
1658
  };
1619
1659
  const prev = ctx.blockModels.get(slug);
1620
1660
  const keep = () => {
@@ -1784,13 +1824,14 @@ function conditionToShowIf(h, at, siblings, ctx, variantOk) {
1784
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). */
1785
1825
  function conditionRule(r, head, siblings, variantOk) {
1786
1826
  const variant = variantOk && r.field === "_variant";
1787
- 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;
1788
1828
  const allowed = head ? [
1789
1829
  "field",
1790
1830
  "equals",
1791
1831
  "notEmpty",
1792
1832
  "op",
1793
1833
  "value",
1834
+ "scope",
1794
1835
  "also",
1795
1836
  "match"
1796
1837
  ] : [
@@ -1798,7 +1839,8 @@ function conditionRule(r, head, siblings, variantOk) {
1798
1839
  "equals",
1799
1840
  "notEmpty",
1800
1841
  "op",
1801
- "value"
1842
+ "value",
1843
+ "scope"
1802
1844
  ];
1803
1845
  if (Object.keys(r).some((k) => !allowed.includes(k) && r[k] !== void 0)) return null;
1804
1846
  if ([
@@ -1806,22 +1848,26 @@ function conditionRule(r, head, siblings, variantOk) {
1806
1848
  r.equals !== void 0,
1807
1849
  r.notEmpty !== void 0
1808
1850
  ].filter(Boolean).length !== 1) return null;
1851
+ const scope = r.scope ? { scope: r.scope } : {};
1809
1852
  if (r.notEmpty !== void 0) return r.notEmpty === true ? {
1810
1853
  field: r.field,
1811
- op: "notEmpty"
1854
+ op: "notEmpty",
1855
+ ...scope
1812
1856
  } : null;
1813
1857
  const target = r.op !== void 0 ? r.value : r.equals;
1814
1858
  const op = r.op !== void 0 ? SHOW_IF_OP[r.op] : Array.isArray(target) ? "in" : "eq";
1815
1859
  if (!op) return null;
1816
1860
  if (VALUELESS.has(op)) return r.value === void 0 ? {
1817
1861
  field: r.field,
1818
- op
1862
+ op,
1863
+ ...scope
1819
1864
  } : null;
1820
- 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;
1821
1866
  if (op === "gt" || op === "lt") return typeof target === "number" && Number.isFinite(target) ? {
1822
1867
  field: r.field,
1823
1868
  op,
1824
- value: String(target)
1869
+ value: String(target),
1870
+ ...scope
1825
1871
  } : null;
1826
1872
  const list = Array.isArray(target) ? target : [target];
1827
1873
  const strs = [];
@@ -1838,16 +1884,19 @@ function conditionRule(r, head, siblings, variantOk) {
1838
1884
  if (op === "in" || op === "notIn") return {
1839
1885
  field: r.field,
1840
1886
  op,
1841
- value: strs
1887
+ value: strs,
1888
+ ...scope
1842
1889
  };
1843
1890
  return Array.isArray(target) ? strs.length ? {
1844
1891
  field: r.field,
1845
1892
  op: op === "eq" ? "in" : "notIn",
1846
- value: strs
1893
+ value: strs,
1894
+ ...scope
1847
1895
  } : null : {
1848
1896
  field: r.field,
1849
1897
  op,
1850
- value: strs[0]
1898
+ value: strs[0],
1899
+ ...scope
1851
1900
  };
1852
1901
  }
1853
1902
  var nonEmpty = (o) => Object.keys(o).length ? o : void 0;
@@ -1864,6 +1913,7 @@ function common(n) {
1864
1913
  "showIf",
1865
1914
  "helpMore",
1866
1915
  "advanced",
1916
+ "ai",
1867
1917
  "recommendedMax",
1868
1918
  "openMode",
1869
1919
  "localized",
@@ -1957,9 +2007,12 @@ function fromTypeSchema(v2, target) {
1957
2007
  blockModels: /* @__PURE__ */ new Map()
1958
2008
  };
1959
2009
  const props = target === "props";
2010
+ if (!props && v2.variants?.length) ctx.variants = true;
1960
2011
  const slug = kebab(v2.name);
1961
- 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);
1962
2014
  const fieldsets = [];
2015
+ const block = {};
1963
2016
  for (const k of Object.keys(v2)) {
1964
2017
  if (v2[k] === void 0) continue;
1965
2018
  const p = `/${k}`;
@@ -1973,23 +2026,22 @@ function fromTypeSchema(v2, target) {
1973
2026
  case "fieldsets":
1974
2027
  case "groups": break;
1975
2028
  case "kind":
1976
- if (props && v2.kind !== "block") cat(ctx, p, v2.kind === "global" ? "kind:global" : "kind:entry (props)");
1977
- 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;
1978
2030
  break;
1979
2031
  case "icon":
1980
- if (!props || !dockIconOf(v2.icon)) cat(ctx, p, "block icon");
1981
- break;
1982
- case "preview":
1983
- if (!props) cat(ctx, p, "document preview");
2032
+ if (!dockIconOf(v2.icon)) cat(ctx, p, "block icon");
1984
2033
  break;
2034
+ case "preview": break;
1985
2035
  case "variants":
1986
- cat(ctx, p, "variants");
2036
+ if (!ctx.variants) cat(ctx, p, "variants");
1987
2037
  break;
1988
2038
  case "category":
1989
- 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");
1990
2042
  break;
1991
2043
  case "placement":
1992
- cat(ctx, p, "block placement");
2044
+ block.placement = v2.placement;
1993
2045
  break;
1994
2046
  case "initialValue":
1995
2047
  cat(ctx, p, "seeded instance");
@@ -2010,6 +2062,7 @@ function fromTypeSchema(v2, target) {
2010
2062
  };
2011
2063
  if (v2.description !== void 0) component.description = v2.description;
2012
2064
  if (category) component.category = category;
2065
+ if (nonEmpty(block)) component.v2 = { ...block };
2013
2066
  const icon = v2.icon === void 0 ? void 0 : dockIconOf(v2.icon);
2014
2067
  if (icon) component.icon = icon;
2015
2068
  if (v2.preview !== void 0) {
@@ -2030,7 +2083,35 @@ function fromTypeSchema(v2, target) {
2030
2083
  fields: nodes.map(emitField)
2031
2084
  };
2032
2085
  if (v2.description !== void 0) model.description = v2.description;
2033
- 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
+ }
2034
2115
  if (fieldsets.length) model.fieldsets = fieldsets;
2035
2116
  return {
2036
2117
  schema: model,
@@ -2414,9 +2495,12 @@ function previewFromProd(pv, children) {
2414
2495
  function showIfToCondition(s, siblings, at, ctx) {
2415
2496
  const conds = [];
2416
2497
  for (const r of s.rules) {
2417
- const c = showIfRuleToCondition(r, siblings);
2498
+ const c = showIfRuleToCondition(r, r.scope ? [] : siblings);
2418
2499
  if (!c) return void ctx.onLoss(at, "condition", `V2 conditions have no "${r.op}" with this value; the field always shows.`);
2419
- conds.push(c);
2500
+ conds.push(r.scope ? {
2501
+ ...c,
2502
+ scope: r.scope
2503
+ } : c);
2420
2504
  }
2421
2505
  if (!conds.length) return void 0;
2422
2506
  const [head, ...also] = conds;
@@ -2530,6 +2614,7 @@ function readToV2(n, at, siblings, ctx, top, lane) {
2530
2614
  if (n.unique !== void 0) val.unique = n.unique;
2531
2615
  if (n.recommendedMax !== void 0) val.recommendedMax = n.recommendedMax;
2532
2616
  if (n.advanced !== void 0) f.advanced = n.advanced;
2617
+ if (n.ai !== void 0) f.ai = n.ai;
2533
2618
  if (n.openMode !== void 0) f.openMode = n.openMode;
2534
2619
  if (n.readOnly === true) f.readOnly = true;
2535
2620
  if (n.archived === true) f.archived = true;
@@ -2569,7 +2654,7 @@ function readToV2(n, at, siblings, ctx, top, lane) {
2569
2654
  } else loss("fieldsetId", "fieldset", `No fieldset "${n.fieldsetId}" exists on this model; the membership is dropped.`);
2570
2655
  }
2571
2656
  if (lane === "props" && n.ui.group) f.fieldset = n.ui.group.id;
2572
- 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;
2573
2658
  for (const k of [
2574
2659
  "target",
2575
2660
  "stale",
@@ -2593,6 +2678,7 @@ function readToV2(n, at, siblings, ctx, top, lane) {
2593
2678
  ]);
2594
2679
  const use = (...k) => k.forEach((x) => used.add(x));
2595
2680
  const useUi = (...k) => k.forEach((x) => usedUi.add(x));
2681
+ if (!top && lane === "fields") useUi("tab");
2596
2682
  if (lane === "props") useUi("group");
2597
2683
  if (lane === "props") useUi("tab");
2598
2684
  if (RICH_TYPES.has(n.type) && cfg.references !== void 0) {
@@ -2815,6 +2901,7 @@ function readToV2(n, at, siblings, ctx, top, lane) {
2815
2901
  };
2816
2902
  if (chrome?.icon) item.icon = tablerIconOf(chrome.icon);
2817
2903
  if (chrome?.preview) item.preview = previewFromProd(chrome.preview, kids);
2904
+ if (chrome?.openMode) item.openMode = chrome.openMode;
2818
2905
  return item;
2819
2906
  });
2820
2907
  const missing = slugs.filter((s) => !ctx.blockModels.has(s));
@@ -2972,14 +3059,27 @@ function toTypeSchema(model, opts = {}) {
2972
3059
  if (s.icon !== void 0) ctx.onLoss(`/fieldsets/${i}/icon`, "fieldset icon", "V2 sections have no icon.");
2973
3060
  fieldsets.push(fs);
2974
3061
  });
2975
- 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);
2976
3064
  const out = {
2977
3065
  name: camel(model.slug),
2978
3066
  title: model.name,
2979
- kind: model.kind === "block" ? "block" : "entry",
3067
+ kind: model.kind === "block" ? "block" : model.v2?.shape ?? "entry",
2980
3068
  fields: nodes.map((n, i) => readToV2(n, `/fields/${i}`, nodes, ctx, true, "fields"))
2981
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;
2982
3081
  if (model.description !== void 0) out.description = model.description;
3082
+ if (model.icon) out.icon = tablerIconOf(model.icon);
2983
3083
  if (fieldsets.length) out.fieldsets = fieldsets;
2984
3084
  if (groups.length) out.groups = groups;
2985
3085
  return out;
@@ -3048,18 +3148,72 @@ function propsToTypeSchema(component, opts = {}) {
3048
3148
  const out = {
3049
3149
  name: camel(component.slug),
3050
3150
  title: component.name,
3051
- kind: "block",
3151
+ kind: component.v2?.shape ?? "block",
3052
3152
  fields: nodes.map((n, i) => readToV2(n, `/fields/${i}`, nodes, ctx, true, "props"))
3053
3153
  };
3154
+ if (component.v2?.placement) out.placement = component.v2.placement;
3054
3155
  if (component.description !== void 0 && component.description !== null) out.description = component.description;
3055
- 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);
3056
3157
  if (component.icon) out.icon = tablerIconOf(component.icon);
3057
3158
  if (component.preview) out.preview = previewFromProd(component.preview, nodes);
3058
3159
  if (fieldsets.length) out.fieldsets = fieldsets;
3059
3160
  if (groups.length) out.groups = groups;
3060
3161
  return out;
3061
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
+ }
3062
3216
  //#endregion
3063
- 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 };
3064
3218
 
3065
3219
  //# sourceMappingURL=schema.js.map