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

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.
@@ -1 +1 @@
1
- {"version":3,"file":"prod-types.js","names":[],"sources":["../../src/adapters/prod-types.ts"],"sourcesContent":["/**\n * PROD SHAPES — the production schema language and stored values, typed locally. The caps and key rules the backend\n * and the kit must agree on come from @bettercms-ai/types (W1a, a peer of the kit since W1c): one home per constant.\n *\n * Mirrors the backend at F1 + F1.x-props + link objects + tags / rating / code (72ea2b68d), then reference props\n * (73a120c50), validation parity (dd021ceba), option meta (ab15fe893), V2 chrome keys and types (98e48b9e3, 324580b2e),\n * the V2 icon vocabulary (e126bf33d) and the localized flag (279a10490) — origin/dev 81f385ff3:\n * packages/types/src/content-model.ts, component.ts, link-value.ts, button-look.ts, dock-icon-names.ts, video-value.ts\n * src/lib/validation/schemas/content.ts (baseField, fieldUiSchema, chromeUiProblems, fieldsets, MAX_FIELD_DEPTH)\n * src/lib/validation/schemas/components.ts (componentPropDefSchema, subFieldSchema, propUiSchema, propValidationHardening)\n * src/lib/content/validation-parity.ts, src/lib/content/dock-vocabulary.ts (TAB_MAX, TAB_NAME_MAX)\n * When the backend widens, widen these and the adapters together; the round-trip test then shrinks the lossy ledger.\n */\nimport { DEPTH_MAX, HELP_CAPS, TAB_MAX as SHARED_TAB_MAX, TAB_MAX_RENDERABLE_NOW } from \"@bettercms-ai/types\";\n\n/* ── Limits the backend enforces ─────────────────────────────────────── */\n/** Field levels one schema may nest (a top-level field, or a prop, is level 1). */\nexport const MAX_FIELD_DEPTH = DEPTH_MAX;\n/** Tabs (`kind: \"tab\"` fieldsets) a model screen holds TODAY (it rises to types' TAB_MAX at F12), and a tab name's length. */\nexport const TAB_MAX = TAB_MAX_RENDERABLE_NOW;\nexport const TAB_NAME_MAX = 40;\nexport const FIELDSETS_MAX = 30;\nexport const FIELDSET_NAME_MAX = 60;\nexport const FIELDSET_DESCRIPTION_MAX = HELP_CAPS.fieldset;\nexport const FIELD_HELP_MAX = HELP_CAPS.field;\nexport const PROP_HELP_MAX = HELP_CAPS.prop;\nexport const PROP_PLACEHOLDER_MAX = HELP_CAPS.placeholder;\nexport const HELP_MORE_MAX = HELP_CAPS.helpMore;\nexport const LOCK_REASON_MAX = 200;\nexport const ITEM_LABEL_MAX = 24;\nexport const PREVIEW_FALLBACK_MAX = 60;\nexport const PROP_GROUP_LABEL_MAX = 32;\nexport const PATTERN_MAX = 200;\n/** A tags value's tag length (TAG_VALUE_MAX_LENGTH), a code value's length (CODE_MAX_LENGTH), a rating's max with none set. */\nexport const TAG_VALUE_MAX_LENGTH = 64;\nexport const CODE_MAX_LENGTH = 100_000;\nexport const RATING_DEFAULT_MAX = 5;\n/** ratingConfigSchema: config.max a whole 1–10; (max − min) / step whole and at most this many stops. */\nexport const RATING_MAX = 10;\nexport const RATING_MAX_STOPS = 100;\n/** tagsConfigSchema: config.min 0–500, config.max 1–500. */\nexport const TAGS_ITEMS_MAX = 500;\n/** A choice option's description (OPTION_DESCRIPTION_MAX): one line. */\nexport const OPTION_DESCRIPTION_MAX = HELP_CAPS.option;\n/** config.patternMessage (PATTERN_MESSAGE_MAX), config.unit on a number (NUMBER_UNIT_MAX). */\nexport const PATTERN_MESSAGE_MAX = 200;\nexport const NUMBER_UNIT_MAX = 24;\n/** fieldUiSchema: ui.rows 1–40, ui.placeholder 1–120, ui.itemTitle 1–60, ui.synced.from 1–64. */\nexport const UI_ROWS_MAX = 40;\nexport const UI_PLACEHOLDER_MAX = HELP_CAPS.placeholder;\nexport const ITEM_TITLE_MAX = 60;\nexport const SYNCED_FROM_MAX = 64;\n/** ui.itemName is an identifier: a letter, then letters, digits or _ (≤ 64). */\nexport const ITEM_NAME_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;\n/** propUiSchema group: description 1–200, order 0–99. */\nexport const PROP_GROUP_DESCRIPTION_MAX = HELP_CAPS.propGroup;\nexport const PROP_GROUP_ORDER_MAX = 99;\n/** propUiSchema tab: at most 8 distinct tabs per component, an id 1–40, a label ≤ TAB_NAME_MAX trimmed, order 0–99. */\nexport const PROP_TAB_MAX = SHARED_TAB_MAX;\nexport const PROP_TAB_ID_MAX = 40;\n/** numberScaleSchema: token value 1–32, label 1–24, title 1–60, css 1–60; 1–50 steps; at most 20 presets. */\nexport const SCALE_TOKEN_VALUE_MAX = 32;\nexport const SCALE_LABEL_MAX = 24;\nexport const SCALE_TITLE_MAX = 60;\nexport const SCALE_CSS_MAX = 60;\nexport const SCALE_STEPS_MAX = 50;\nexport const SCALE_PRESETS_MAX = 20;\n/** The video block's transcript cap (VIDEO_TRANSCRIPT_MAX). */\nexport const VIDEO_TRANSCRIPT_MAX = 100_000;\n\n/* ── Icons: the closed dock vocabulary (packages/types/src/dock-icon-names.ts at e126bf33d: 472 names, Sanity's set\n * plus every distinct V2 corpus icon but `map`; copied verbatim from that generated file, regenerate from it) ── */\nexport const DOCK_ICON_NAMES = [\n \"abc\", \"access-denied\", \"activity\", \"add\", \"add-circle\", \"add-comment\", \"add-document\", \"add-user\", \"address-book\",\n \"adjustments\", \"adjustments-horizontal\", \"alarm\", \"alert-triangle\", \"align-left\", \"api\", \"app-window\", \"apple\",\n \"apps\", \"archive\", \"arrow-back-up\", \"arrow-down\", \"arrow-guide\", \"arrow-left\", \"arrow-right\", \"arrow-top-right\",\n \"arrow-up\", \"arrows-diagonal\", \"arrows-down\", \"arrows-exchange\", \"arrows-horizontal\", \"arrows-left-right\",\n \"arrows-shuffle\", \"arrows-sort\", \"article\", \"aspect-ratio\", \"asterisk\", \"award\", \"axis-x\", \"backpack\", \"badge\",\n \"badge-cc\", \"badges\", \"bar-chart\", \"basket\", \"bell\", \"bill\", \"binary-document\", \"block-content\", \"block-element\",\n \"blockquote\", \"bold\", \"bolt\", \"book\", \"book-2\", \"bookmark\", \"bookmark-filled\", \"books\", \"bottle\", \"box\",\n \"brand-dribbble\", \"brand-facebook\", \"brand-instagram\", \"brand-linkedin\", \"brand-pinterest\", \"brand-tiktok\",\n \"brand-x\", \"brand-youtube\", \"briefcase\", \"broadcast\", \"browser\", \"bug\", \"building\", \"building-skyscraper\",\n \"building-store\", \"bulb-filled\", \"bulb-outline\", \"calculator\", \"calendar\", \"calendar-dollar\", \"calendar-event\",\n \"calendar-exclamation\", \"calendar-off\", \"calendar-plus\", \"calendar-star\", \"calendar-time\", \"camera\", \"campfire\",\n \"carousel-horizontal\", \"case\", \"cash\", \"category\", \"category-2\", \"chart-area\", \"chart-arrows\", \"chart-bar\",\n \"chart-bar-popular\", \"chart-donut\", \"chart-dots\", \"chart-line\", \"chart-upward\", \"check\", \"checkbox\", \"checklist\",\n \"checkmark\", \"checkmark-circle\", \"chef-hat\", \"chevron-down\", \"chevron-left\", \"chevron-right\", \"chevron-up\",\n \"chevrons-right\", \"circle\", \"circle-check\", \"circle-dot\", \"circle-number-1\", \"circle-plus\", \"click\", \"clipboard\",\n \"clipboard-image\", \"clipboard-text\", \"clock\", \"clock-check\", \"clock-hour-4\", \"close\", \"close-circle\", \"code\",\n \"code-block\", \"cog\", \"collapse\", \"color-wheel\", \"columns\", \"columns-2\", \"columns-3\", \"comment\", \"component\",\n \"compose\", \"compose-sparkles\", \"confetti\", \"controls\", \"cookie\", \"copy\", \"credit-card\", \"crop\", \"cube\",\n \"currency-dollar\", \"dashboard\", \"database\", \"database-search\", \"desktop\", \"device-desktop\", \"device-mobile\",\n \"device-tablet\", \"devices\", \"diamond\", \"discount\", \"document\", \"document-pdf\", \"document-remove\", \"document-sheet\",\n \"document-text\", \"document-video\", \"document-word\", \"document-zip\", \"documents\", \"door-exit\", \"dot\",\n \"double-chevron-down\", \"double-chevron-left\", \"double-chevron-right\", \"double-chevron-up\", \"double-quote\",\n \"download\", \"drag-handle\", \"drop\", \"droplet\", \"droplet-half\", \"earth-americas\", \"earth-globe\", \"edit\", \"egg\",\n \"ellipsis-horizontal\", \"ellipsis-vertical\", \"empty\", \"enter\", \"enter-right\", \"envelope\", \"equal\", \"error-filled\",\n \"error-outline\", \"error-screen\", \"expand\", \"eye-closed\", \"eye-off\", \"eye-open\", \"face-happy\", \"face-indifferent\",\n \"face-sad\", \"feather\", \"feedback\", \"file\", \"file-certificate\", \"file-code\", \"file-download\", \"file-text\", \"filter\",\n \"flag\", \"flame\", \"flask\", \"focus-2\", \"folder\", \"folders\", \"forms\", \"frame\", \"generate\", \"gift\", \"git-branch\",\n \"github\", \"grid-dots\", \"groq\", \"hammer\", \"hand-finger\", \"hanger\", \"hash\", \"headphones\", \"headset\", \"heart\",\n \"heart-filled\", \"heart-handshake\", \"help-circle\", \"highlight\", \"history\", \"home\", \"hourglass\", \"ice-cream\",\n \"id-badge\", \"id-badge-2\", \"image\", \"image-remove\", \"images\", \"inbox\", \"infinity\", \"info-circle\", \"info-filled\",\n \"info-outline\", \"info-square-rounded\", \"inline\", \"inline-element\", \"input-search\", \"insert-above\", \"insert-below\",\n \"italic\", \"joystick\", \"json\", \"language\", \"launch\", \"layout\", \"layout-align-center\", \"layout-board\",\n \"layout-board-split\", \"layout-bottombar\", \"layout-bottombar-expand\", \"layout-cards\", \"layout-columns\",\n \"layout-dashboard\", \"layout-grid\", \"layout-list\", \"layout-navbar\", \"layout-rows\", \"layout-sidebar\",\n \"layout-sidebar-right\", \"leaf\", \"leave\", \"lemon\", \"library-photo\", \"lifebuoy\", \"link\", \"link-removed\", \"linkedin\",\n \"list\", \"list-check\", \"list-details\", \"list-numbers\", \"list-tree\", \"lock\", \"logo-js\", \"logo-ts\", \"mail\", \"map-2\",\n \"map-pin\", \"map-pins\", \"marker\", \"marker-removed\", \"master-detail\", \"math-function\", \"meat\", \"menu\", \"message\",\n \"message-2\", \"message-check\", \"message-circle\", \"message-circle-2\", \"message-plus\", \"message-star\", \"microphone\",\n \"microphone-slash\", \"mobile-device\", \"mood-empty\", \"moon\", \"movie\", \"news\", \"notes\", \"number\", \"ok-hand\", \"olist\",\n \"overage\", \"package\", \"packages\", \"palette\", \"panel-left\", \"panel-right\", \"paperclip\", \"pause\", \"pencil\", \"phone\",\n \"photo\", \"photo-scan\", \"photo-search\", \"pig-money\", \"pin\", \"pin-filled\", \"pin-removed\", \"play\", \"player-play\",\n \"player-track-next\", \"playlist-add\", \"plug\", \"plug-connected\", \"plus-minus\", \"point\", \"presentation\", \"printer\",\n \"progress-50\", \"progress-75\", \"projects\", \"publish\", \"qrcode\", \"quote\", \"read-only\", \"receipt\", \"recycle\", \"redo\",\n \"refresh\", \"remove\", \"remove-circle\", \"repeat\", \"reset\", \"restore\", \"retrieve\", \"retry\", \"revert\", \"robot\",\n \"rocket\", \"rosette\", \"route\", \"ruler\", \"ruler-measure\", \"salad\", \"scale\", \"schema\", \"school\", \"search\", \"section\",\n \"select\", \"send\", \"server\", \"settings\", \"share\", \"shield-check\", \"shield-lock\", \"shopping-bag\", \"shopping-cart\",\n \"shopping-cart-plus\", \"signature\", \"sitemap\", \"slideshow\", \"sort\", \"sort-descending\", \"spacing-vertical\", \"sparkle\",\n \"sparkles\", \"speakerphone\", \"spinner\", \"split-horizontal\", \"split-vertical\", \"square\", \"stack\", \"stack-2\",\n \"stack-compact\", \"stairs\", \"stairs-up\", \"star\", \"star-filled\", \"stars\", \"stop\", \"strikethrough\", \"string\",\n \"subtask\", \"sun\", \"superscript\", \"sync\", \"table\", \"table-options\", \"tablet-device\", \"tag\", \"tags\", \"target\", \"task\",\n \"template\", \"tent\", \"terminal\", \"terminal-2\", \"text\", \"th-large\", \"th-list\", \"thumb-up\", \"thumbs-down\", \"thumbs-up\",\n \"ticket\", \"tiers\", \"timeline\", \"toggle-arrow-right\", \"toggle-right\", \"token\", \"tool\", \"tools-kitchen-2\", \"transfer\",\n \"translate\", \"trash\", \"trend-upward\", \"triangle-outline\", \"trolley\", \"truck\", \"truck-delivery\", \"truncate\",\n \"twitter\", \"typography\", \"ulist\", \"unarchive\", \"underline\", \"undo\", \"unknown\", \"unlink\", \"unlock\", \"unpublish\",\n \"upload\", \"user\", \"user-circle\", \"user-plus\", \"user-search\", \"user-star\", \"users\", \"users-group\", \"versions\",\n \"video\", \"vocabulary\", \"vs\", \"warning-filled\", \"warning-outline\", \"weight\", \"world\", \"wrench\", \"zoom-question\",\n] as const;\nexport type DockIconName = (typeof DOCK_ICON_NAMES)[number];\nexport const DOCK_ICON_NAME_SET: ReadonlySet<string> = new Set<string>(DOCK_ICON_NAMES);\n\n/* ── Content model fields ────────────────────────────────────────────── */\nexport type ProdFieldType =\n | \"text\" | \"richtext\" | \"document\" | \"image\" | \"boolean\" | \"number\" | \"select\"\n | \"reference\" | \"multi-reference\" | \"multiReference\" | \"array\" | \"date\" | \"datetime\"\n | \"longtext\" | \"slug\" | \"email\" | \"phone\" | \"link\" | \"color\" | \"json\" | \"file\"\n | \"component-ref\" | \"modular\" | \"sections\" | \"location\" | \"button\" | \"multiselect\" | \"icon\" | \"object\"\n | \"tags\" | \"rating\" | \"code\"\n /** V2 video: a URL string or `{ url, poster?, autoplay?, loop?, transcript? }` (VideoValue). */\n | \"video\"\n /** Legacy input only: normalized to `array` + zones on write, still read. */\n | \"group\" | \"repeater\";\n\nexport type OpenMode = \"inline\" | \"drill\" | \"popover\" | \"dialog\";\n/** backend SHOW_IF_OPS (validation/schemas/content.ts). */\nexport type ShowIfOp = \"eq\" | \"neq\" | \"in\" | \"notIn\" | \"gt\" | \"lt\" | \"isSet\" | \"notEmpty\" | \"empty\";\nexport interface ShowIfRule {\n field: string;\n op: ShowIfOp;\n value?: string | string[];\n}\nexport interface ShowIf {\n match: \"all\" | \"any\";\n rules: ShowIfRule[];\n}\n\nexport type UiLayout = \"list\" | \"grid\" | \"table\" | \"rows\" | \"chips\" | \"gallery\";\n/** ui.control (UI_CONTROLS): the leaf controls, the V2 control layouts among them. */\nexport const UI_CONTROLS = [\"segmented\", \"slider\", \"rating\", \"dropdown\", \"radio\", \"cards\", \"grid\", \"switch\", \"checkbox\"] as const;\nexport type UiControl = (typeof UI_CONTROLS)[number];\n/** The V2 control layouts and the types each fits (CONTROL_TYPES); segmented / slider / rating have their own rules. */\nexport const CONTROL_TYPES: Readonly<Partial<Record<UiControl, readonly string[]>>> = {\n dropdown: [\"select\"],\n radio: [\"select\"],\n cards: [\"select\", \"multiselect\"],\n grid: [\"select\"],\n switch: [\"boolean\"],\n checkbox: [\"boolean\", \"multiselect\"],\n};\n/** A row preview: child keys or dotted paths (V2), a literal fallback, and an icon shown while there is no media. */\nexport interface UiPreview {\n title?: string;\n subtitle?: string;\n media?: string;\n fallback?: string;\n icon?: DockIconName;\n}\nexport interface FieldUi {\n collapsed?: boolean;\n preview?: UiPreview;\n layout?: UiLayout;\n reorderable?: boolean;\n control?: UiControl;\n icon?: DockIconName;\n itemLabel?: string;\n /** text / longtext: the lines a text box opens with (1–40). */\n rows?: number;\n /** a field's placeholder when its config is strict (a prop keeps its own `placeholder`). */\n placeholder?: string;\n /** a list's item type: its identifier and its title (V2 `of[0].name` / `title`). */\n itemName?: string;\n itemTitle?: string;\n /** the value comes from a global: an edit applies everywhere. */\n synced?: { from: string };\n /** link / image / button: a plain string or the object. */\n valueShape?: \"string\" | \"object\";\n /** V2 field access (D58): the editor gates, the backend stores. */\n access?: { view?: \"schema.view\"; edit?: \"schema.manage\" };\n /** true = never shown in the form; false ≡ absent. */\n hidden?: boolean;\n /** modular: per block slug, how a row of that block type reads (V2 `of[i].icon` / `preview`). */\n blocks?: Record<string, { icon?: DockIconName; preview?: UiPreview }>;\n input?: { name: string; writes?: string[] };\n}\n\nexport type ScaleUnit = \"px\" | \"%\" | \"rem\" | \"em\" | \"vw\" | \"vh\";\nexport interface ScaleCustomValue {\n value: number;\n unit: ScaleUnit;\n}\nexport interface ScaleTokenStep {\n value: string;\n label: string;\n title?: string;\n px?: number;\n css?: string;\n}\nexport type ScaleBound = number | Partial<Record<ScaleUnit, number>>;\n/**\n * A number's `config.scale` (CPO D41): a stepped range, numeric steps or token steps, units, presets, and the custom\n * escape hatch (a boolean, or its bounds). A token scale stores the token string; a custom value `{ value, unit }`.\n */\nexport interface NumberScale {\n steps: { min: number; max: number; step?: number } | Array<{ value: number; label?: string }> | ScaleTokenStep[];\n units?: ScaleUnit[];\n defaultUnit?: ScaleUnit;\n allowCustom?: boolean | { min?: ScaleBound; max?: ScaleBound; units?: ScaleUnit[]; step?: number };\n presets?: Array<{ label?: string; value: number | string | ScaleCustomValue }>;\n}\n\nexport interface ArrayZoneConfig {\n nonRepeatable?: ContentModelField[];\n repeatable?: { fields: ContentModelField[]; minItems?: number; maxItems?: number };\n}\n\n/** One choice: a bare value, or `{ label, value }` with optional chrome (a dock icon, one line ≤ 140 under the label). */\nexport interface ChoiceOption {\n label: string;\n value: string;\n icon?: DockIconName;\n description?: string;\n}\nexport type SelectOption = string | ChoiceOption;\n\n/**\n * A list of scalars' item settings (backend arrayItemSettingsSchema, an `array` prop's `config.item`): the V2 item field's\n * description, locale chip, required, and a text / rich text item's length (a text item's pattern).\n */\nexport interface ArrayItemSettings {\n helpText?: string;\n localized?: boolean;\n required?: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n patternMessage?: string;\n}\n\n/** The typed keys of a field's `config` bag; the bag itself stays loose on scalar types. */\nexport interface FieldConfig {\n /** primitive array: scalars, or images, links or rich text (each item judged by its own type). */\n itemType?: \"text\" | \"number\" | \"date\" | \"image\" | \"link\" | \"richtext\";\n zones?: ArrayZoneConfig;\n contentModelId?: string;\n polymorphic?: boolean;\n allowedModelSlugs?: string[];\n /** reference / multi-reference (props): the non-entry targets, and a filter over the target's fields. */\n targets?: (\"page\" | \"form\" | \"collection\")[];\n filter?: string;\n min?: number;\n max?: number;\n step?: number;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n /** text / longtext: the message shown when the pattern does not match (≤ 200, needs `pattern`). */\n patternMessage?: string;\n /** number: the error bounds (V2 validation.min / max); they win over min / max. */\n validation?: { min?: number; max?: number };\n /** number: what it counts (\"%\", \"USD\"), 1–24 characters. */\n unit?: string;\n placeholder?: string;\n includeTime?: boolean;\n /** \"site\", an IANA zone name, true (the editor picks a zone) or false (none). */\n timezone?: string | boolean;\n blockSlugs?: string[];\n minItems?: number;\n maxItems?: number;\n allowedSections?: string[];\n componentIds?: string[];\n /** slot (props): the V2 `of.allow` block names it takes, kept as written (backend slotAllowIssues). */\n allow?: string[];\n /** array (props): the item's own settings (backend arrayItemSettingsSchema). */\n item?: ArrayItemSettings;\n scale?: NumberScale;\n /** multiselect: its choices; with `allowCustom` it is free tags. tags: its suggestions. */\n options?: SelectOption[];\n allowCustom?: boolean;\n /** code: the language, and the languages an editor may pick (strict config, with tags' and rating's). */\n language?: string;\n languages?: string[];\n /**\n * text: \"url\" marks a text field the kit edits as a URL (V2 `url`). The backend keeps a text field's config as sent and\n * enforces its minLength / maxLength / pattern, which a link field would not; it reads no other meaning into the key.\n */\n inputType?: \"url\";\n [key: string]: unknown;\n}\n\nexport interface ContentModelField {\n key: string;\n label: string;\n type: ProdFieldType;\n helpText?: string;\n required?: boolean;\n unique?: boolean;\n defaultValue?: unknown;\n /** select: strings or { label, value }. */\n options?: SelectOption[];\n config?: FieldConfig | null;\n /** object (and legacy group / repeater): child fields. */\n fields?: ContentModelField[];\n openMode?: OpenMode;\n helpMore?: string;\n advanced?: boolean;\n recommendedMax?: number;\n fieldsetId?: string;\n placement?: \"cover\" | \"title\" | \"excerpt\" | \"body\" | \"inline\" | \"panel\";\n showIf?: ShowIf;\n readOnly?: true | \"developer\";\n lockReason?: string;\n /** V2 per-field localization: once any field is marked, a translation owns only its localized values. */\n localized?: boolean;\n ui?: FieldUi;\n searchable?: boolean;\n [key: string]: unknown;\n}\n\nexport type FieldsetKind = \"group\" | \"row\" | \"tab\";\nexport interface Fieldset {\n id: string;\n name: string;\n description?: string;\n kind?: FieldsetKind;\n icon?: DockIconName;\n collapsed?: boolean;\n tab?: string;\n collapsible?: boolean;\n columns?: 1 | 2;\n layout?: \"stack\" | \"breakpoints\";\n}\n\n/** A content model as the adapters read and write it (server-owned ids and timestamps left out). */\nexport interface ProdContentModel {\n name: string;\n slug: string;\n description?: string;\n kind?: \"model\" | \"block\";\n fields: ContentModelField[];\n fieldsets?: Fieldset[];\n}\n\n/* ── Component props ─────────────────────────────────────────────────── */\nexport const COMPONENT_PROP_TYPES = [\n \"text\", \"richtext\", \"image\", \"url\", \"boolean\", \"number\", \"select\", \"group\", \"table\", \"slot\", \"form\",\n \"link\", \"button\", \"date\", \"color\", \"file\", \"icon\", \"multiselect\",\n \"object\", \"datetime\", \"slug\", \"email\", \"phone\", \"json\", \"location\", \"tags\", \"rating\", \"code\",\n /** an entry id (or `{ entryId, modelSlug }`) and a list of them, hydrated on delivery; no definition default. */\n \"reference\", \"multi-reference\",\n /** a video value, a list of `config.itemType` values, multi-line text. */\n \"video\", \"array\", \"longtext\",\n] as const;\nexport type ComponentPropType = (typeof COMPONENT_PROP_TYPES)[number];\nexport type ComponentSubFieldType = Exclude<ComponentPropType, \"slot\" | \"form\">;\n\nexport const COMPONENT_CATEGORIES = [\n \"navbar\", \"footer\", \"button\", \"section\", \"slider\", \"tabs\", \"form\", \"custom\", \"hero\", \"content\", \"social-proof\", \"conversion\",\n] as const;\nexport type ComponentCategory = (typeof COMPONENT_CATEGORIES)[number];\n\nexport interface PropGroup {\n id: string;\n label?: string;\n kind?: \"group\" | \"row\";\n icon?: DockIconName;\n collapsed?: boolean;\n /** V2 fieldset keys, on the defining prop: a line under the header (≤ 200), side by side, a static header, a\n * breakpoint switcher, and its place among the groups (0–99; absent = where its first prop sits). */\n description?: string;\n columns?: 1 | 2;\n collapsible?: boolean;\n layout?: \"stack\" | \"breakpoints\";\n order?: number;\n}\n/**\n * A top-level prop's tab. Defined once: the first top-level prop with an id carries its label / icon / order, later\n * props carry `{ id }` only. A nested prop names one as `{ id }` only (tab_nested refuses a definition there; V2 and\n * the dock ignore it); one ui.group's members share one tab.\n */\nexport interface PropTab {\n id: string;\n label?: string;\n icon?: DockIconName;\n /** its place among the tabs (0–99; absent = where its first prop sits). */\n order?: number;\n}\nexport type PropUi = FieldUi & { group?: PropGroup; tab?: PropTab };\n\nexport interface ComponentSubField {\n key: string;\n label: string;\n type: ComponentSubFieldType;\n defaultValue?: unknown;\n required?: boolean;\n /** null = no help on purpose (V2 has no description); absent = the platform's auto-help. */\n helpText?: string | null;\n placeholder?: string;\n /** strings or { label, value } (subFieldOptionSchema). */\n options?: SelectOption[];\n helpMore?: string;\n advanced?: boolean;\n recommendedMax?: number;\n /** on a table's sub-field: no two rows share it; on a list: no item repeats. */\n unique?: boolean;\n openMode?: OpenMode;\n showIf?: ShowIf;\n /** the prop's ui, inline group included (V2 fieldsets nest). */\n ui?: PropUi;\n readOnly?: true | \"developer\";\n lockReason?: string;\n localized?: boolean;\n fields?: ComponentSubField[];\n config?: { fields?: ComponentSubField[]; options?: SelectOption[] } & Record<string, unknown>;\n}\n\nexport interface ComponentPropDef {\n key: string;\n label: string;\n /**\n * Where the override writes inside the component's blockJson. Binding, not schema: the V2 language has no twin, so\n * fromTypeSchema leaves it unset and the host binds it (componentize lane) before the save.\n */\n target?: { blockId: string; path: string };\n type: ComponentPropType;\n defaultValue?: unknown;\n required?: boolean;\n /** null = no help on purpose (V2 has no description); absent = the platform's auto-help. */\n helpText?: string | null;\n placeholder?: string;\n showIf?: ShowIf;\n helpMore?: string;\n advanced?: boolean;\n recommendedMax?: number;\n /** a list prop's items (a table's rows) distinct; refused on a top-level scalar (unique_prop_scope). */\n unique?: boolean;\n openMode?: OpenMode;\n readOnly?: true | \"developer\";\n lockReason?: string;\n localized?: boolean;\n config?: {\n componentIds?: string[];\n /** slot: the V2 block names it takes (`textBlock`), resolved to the project's components by the dock. */\n allow?: string[];\n /** array: the item's own settings. */\n item?: ArrayItemSettings;\n multiple?: boolean;\n minItems?: number;\n maxItems?: number;\n options?: SelectOption[];\n fields?: ComponentSubField[];\n min?: number;\n max?: number;\n step?: number;\n /** number: the error bounds (they win over min / max). */\n validation?: { min?: number; max?: number };\n /** text / url / longtext / richtext: length (richtext: its plain text). text / url / longtext: a pattern and its message. */\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n patternMessage?: string;\n /** reference / multi-reference: the target model, or polymorphic with an allowlist of model slugs. */\n contentModelId?: string;\n polymorphic?: boolean;\n allowedModelSlugs?: string[];\n /** reference / multi-reference: page / form / collection targets (backend REF_TARGET_KINDS) and a filter over the target's fields. */\n targets?: (\"page\" | \"form\" | \"collection\")[];\n filter?: string;\n /** array: what the list holds. */\n itemType?: \"text\" | \"number\" | \"date\" | \"image\" | \"link\" | \"richtext\";\n /** table with mode \"types\" (a polymorphic list): its item types, each row's `_type` naming one. */\n itemTypes?: { name: string; label?: string; fields: ComponentSubField[]; icon?: DockIconName; preview?: UiPreview }[];\n } & Record<string, unknown>;\n ui?: PropUi;\n}\n\n/** A component as the adapters read and write it. */\n/**\n * How ONE instance of a component reads as a row in a parent's slot list (backend `components.preview`, migration 0459;\n * types ComponentPreview): V2's document-level `preview`. `title` / `subtitle` / `media` each name a TOP-LEVEL prop key\n * (the API refuses any other: preview_key_unknown), `fallback` is the literal row label (trimmed, 1..120), `icon` a dock\n * icon shown while the row has no media.\n */\nexport type ComponentPreview = UiPreview;\n/** backend componentPreviewSchema: a preview slot names a key of at most this many characters */\nexport const COMPONENT_PREVIEW_KEY_MAX = 64;\n/** backend componentPreviewSchema: the row's literal fallback label, trimmed, at most this many characters */\nexport const COMPONENT_PREVIEW_FALLBACK_MAX = 120;\n\nexport interface ProdComponent {\n name: string;\n slug: string;\n description?: string;\n category?: ComponentCategory;\n props: ComponentPropDef[];\n preview?: ComponentPreview | null;\n /** The component's dock icon (backend `components.icon`, dockIconSchema): V2's block icon. */\n icon?: DockIconName | null;\n /** The V2 tabs no top-level prop sits in (backend `components.tabs`, componentTabsSchema): declared, shown by none. */\n tabs?: PropTab[] | null;\n}\n\n/* ── Stored values ───────────────────────────────────────────────────── */\nexport const LINK_RELS = [\"nofollow\", \"sponsored\", \"ugc\"] as const;\nexport type LinkRel = (typeof LINK_RELS)[number];\nexport const LINK_VALUE_TYPES = [\"url\", \"page\", \"entry\", \"email\", \"phone\", \"anchor\", \"section\", \"file\"] as const;\nexport type LinkValueType = (typeof LINK_VALUE_TYPES)[number];\n/** The F1 link object (link-value.ts). A bare href string stays valid too. */\nexport interface ProdLinkValue {\n type?: LinkValueType;\n href: string;\n title?: string;\n newTab?: boolean;\n target?: \"_self\" | \"_blank\";\n rel?: LinkRel;\n section?: string;\n /** page / entry links: the target's id. */\n ref?: string;\n}\n\nexport const BUTTON_VARIANTS = [\"primary\", \"secondary\", \"tertiary\", \"brand\", \"dark\", \"light\", \"outline\", \"link\"] as const;\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number];\nexport const BUTTON_SIZES = [\"sm\", \"md\", \"lg\"] as const;\n/** One button as one value (button-look.ts ButtonValue). */\nexport interface ProdButtonValue {\n label: string;\n href: string | ProdLinkValue;\n variant?: ButtonVariant;\n size?: (typeof BUTTON_SIZES)[number];\n arrow?: boolean;\n icon?: DockIconName | null;\n newTab?: boolean;\n rel?: LinkRel;\n /** Stretch across the column (backend button_full_width_invalid refuses a non-boolean a write brings). */\n fullWidth?: boolean;\n}\n\n/** 0–1 fractions of the image cut from each edge. */\nexport interface ProdImageCrop {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n/** The normalized image value (field-value-normalize.ts): other keys are dropped on write. */\nexport interface ProdImageValue {\n id?: string;\n url: string;\n name?: string;\n altText?: string;\n /** 0–1 fractions. */\n focal?: { x: number; y: number };\n crop?: ProdImageCrop;\n}\n\n/** The V2 video value's object form (video-value.ts): exactly the video block's props. A bare URL string is valid too. */\nexport const VIDEO_VALUE_KEYS = [\"url\", \"poster\", \"autoplay\", \"loop\", \"transcript\"] as const;\nexport interface ProdVideoValue {\n url: string;\n poster?: string;\n autoplay?: boolean;\n loop?: boolean;\n transcript?: string;\n}\n\n/** A polymorphic reference value. */\nexport interface ProdPolymorphicRef {\n entryId: string;\n modelSlug: string;\n}\n/**\n * A page, form or collection pick on a prop reference that also takes entries (backend reference-target-config.ts\n * targetRefOf): a bare string there is read as an entry id (acceptsEntries), so a non-entry pick names its kind.\n */\nexport interface ProdTargetRef {\n kind: \"page\" | \"form\" | \"collection\";\n id: string;\n}\n/** What a `form` prop stores. */\nexport interface ProdFormRef {\n formId: string;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"prod-types.js","names":[],"sources":["../../src/adapters/prod-types.ts"],"sourcesContent":["/**\n * PROD SHAPES — the production schema language and stored values, typed locally. The caps and key rules the backend\n * and the kit must agree on come from @bettercms-ai/types (W1a, a peer of the kit since W1c): one home per constant.\n *\n * Mirrors the backend at F1 + F1.x-props + link objects + tags / rating / code (72ea2b68d), then reference props\n * (73a120c50), validation parity (dd021ceba), option meta (ab15fe893), V2 chrome keys and types (98e48b9e3, 324580b2e),\n * the V2 icon vocabulary (e126bf33d) and the localized flag (279a10490) — origin/dev 81f385ff3:\n * packages/types/src/content-model.ts, component.ts, link-value.ts, button-look.ts, dock-icon-names.ts, video-value.ts\n * src/lib/validation/schemas/content.ts (baseField, fieldUiSchema, chromeUiProblems, fieldsets, MAX_FIELD_DEPTH)\n * src/lib/validation/schemas/components.ts (componentPropDefSchema, subFieldSchema, propUiSchema, propValidationHardening)\n * src/lib/content/validation-parity.ts, src/lib/content/dock-vocabulary.ts (TAB_MAX, TAB_NAME_MAX)\n * When the backend widens, widen these and the adapters together; the round-trip test then shrinks the lossy ledger.\n */\nimport { DEPTH_MAX, HELP_CAPS, TAB_MAX as SHARED_TAB_MAX, type KitCategory, type V2Placement, type V2Shape } from \"@bettercms-ai/types\";\n\n/* ── Limits the backend enforces ─────────────────────────────────────── */\n/** Field levels one schema may nest (a top-level field, or a prop, is level 1). */\nexport const MAX_FIELD_DEPTH = DEPTH_MAX;\n/** Tabs (`kind: \"tab\"` fieldsets) a model screen holds (types' TAB_MAX: no render cap, the dock folds the rest into More), and a tab name's length. */\nexport const TAB_MAX = SHARED_TAB_MAX;\nexport const TAB_NAME_MAX = 40;\nexport const FIELDSETS_MAX = 30;\nexport const FIELDSET_NAME_MAX = 60;\nexport const FIELDSET_DESCRIPTION_MAX = HELP_CAPS.fieldset;\nexport const FIELD_HELP_MAX = HELP_CAPS.field;\nexport const PROP_HELP_MAX = HELP_CAPS.prop;\nexport const PROP_PLACEHOLDER_MAX = HELP_CAPS.placeholder;\nexport const HELP_MORE_MAX = HELP_CAPS.helpMore;\nexport const LOCK_REASON_MAX = 200;\nexport const ITEM_LABEL_MAX = 24;\nexport const PREVIEW_FALLBACK_MAX = 60;\nexport const PROP_GROUP_LABEL_MAX = 32;\nexport const PATTERN_MAX = 200;\n/** A tags value's tag length (TAG_VALUE_MAX_LENGTH), a code value's length (CODE_MAX_LENGTH), a rating's max with none set. */\nexport const TAG_VALUE_MAX_LENGTH = 64;\nexport const CODE_MAX_LENGTH = 100_000;\nexport const RATING_DEFAULT_MAX = 5;\n/** ratingConfigSchema: config.max a whole 1–10; (max − min) / step whole and at most this many stops. */\nexport const RATING_MAX = 10;\nexport const RATING_MAX_STOPS = 100;\n/** tagsConfigSchema: config.min 0–500, config.max 1–500. */\nexport const TAGS_ITEMS_MAX = 500;\n/** A choice option's description (OPTION_DESCRIPTION_MAX): one line. */\nexport const OPTION_DESCRIPTION_MAX = HELP_CAPS.option;\n/** config.patternMessage (PATTERN_MESSAGE_MAX), config.unit on a number (NUMBER_UNIT_MAX). */\nexport const PATTERN_MESSAGE_MAX = 200;\nexport const NUMBER_UNIT_MAX = 24;\n/** fieldUiSchema: ui.rows 1–40, ui.placeholder 1–120, ui.itemTitle 1–60, ui.synced.from 1–64. */\nexport const UI_ROWS_MAX = 40;\nexport const UI_PLACEHOLDER_MAX = HELP_CAPS.placeholder;\nexport const ITEM_TITLE_MAX = 60;\nexport const SYNCED_FROM_MAX = 64;\n/** ui.itemName is an identifier: a letter, then letters, digits or _ (≤ 64). */\nexport const ITEM_NAME_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;\n/** propUiSchema group: description 1–200, order 0–99. */\nexport const PROP_GROUP_DESCRIPTION_MAX = HELP_CAPS.propGroup;\nexport const PROP_GROUP_ORDER_MAX = 99;\n/** propUiSchema tab: at most 8 distinct tabs per component, an id 1–40, a label ≤ TAB_NAME_MAX trimmed, order 0–99. */\nexport const PROP_TAB_MAX = SHARED_TAB_MAX;\nexport const PROP_TAB_ID_MAX = 40;\n/** numberScaleSchema: token value 1–32, label 1–24, title 1–60, css 1–60; 1–50 steps; at most 20 presets. */\nexport const SCALE_TOKEN_VALUE_MAX = 32;\nexport const SCALE_LABEL_MAX = 24;\nexport const SCALE_TITLE_MAX = 60;\nexport const SCALE_CSS_MAX = 60;\nexport const SCALE_STEPS_MAX = 50;\nexport const SCALE_PRESETS_MAX = 20;\n/** The video block's transcript cap (VIDEO_TRANSCRIPT_MAX). */\nexport const VIDEO_TRANSCRIPT_MAX = 100_000;\n\n/* ── Icons: the closed dock vocabulary (packages/types/src/dock-icon-names.ts at 282655543: 473 names, Sanity's set\n * plus every distinct V2 corpus icon but `map`; copied verbatim from that generated file, regenerate from it) ── */\nexport const DOCK_ICON_NAMES = [\n \"abc\", \"access-denied\", \"activity\", \"add\", \"add-circle\", \"add-comment\", \"add-document\", \"add-user\", \"address-book\",\n \"adjustments\", \"adjustments-horizontal\", \"alarm\", \"alert-triangle\", \"align-left\", \"api\", \"app-window\", \"apple\",\n \"apps\", \"archive\", \"arrow-back-up\", \"arrow-down\", \"arrow-guide\", \"arrow-left\", \"arrow-right\", \"arrow-top-right\",\n \"arrow-up\", \"arrows-diagonal\", \"arrows-down\", \"arrows-exchange\", \"arrows-horizontal\", \"arrows-left-right\",\n \"arrows-shuffle\", \"arrows-sort\", \"article\", \"aspect-ratio\", \"asterisk\", \"award\", \"axis-x\", \"backpack\", \"badge\",\n \"badge-cc\", \"badges\", \"bar-chart\", \"basket\", \"bell\", \"bill\", \"binary-document\", \"block-content\", \"block-element\",\n \"blockquote\", \"bold\", \"bolt\", \"book\", \"book-2\", \"bookmark\", \"bookmark-filled\", \"books\", \"bottle\", \"box\",\n \"brand-dribbble\", \"brand-facebook\", \"brand-instagram\", \"brand-linkedin\", \"brand-pinterest\", \"brand-tiktok\",\n \"brand-x\", \"brand-youtube\", \"briefcase\", \"broadcast\", \"browser\", \"bug\", \"building\", \"building-skyscraper\",\n \"building-store\", \"bulb-filled\", \"bulb-outline\", \"calculator\", \"calendar\", \"calendar-dollar\", \"calendar-event\",\n \"calendar-exclamation\", \"calendar-off\", \"calendar-plus\", \"calendar-star\", \"calendar-time\", \"camera\", \"campfire\",\n \"carousel-horizontal\", \"case\", \"cash\", \"category\", \"category-2\", \"chart-area\", \"chart-arrows\", \"chart-bar\",\n \"chart-bar-popular\", \"chart-donut\", \"chart-dots\", \"chart-line\", \"chart-upward\", \"check\", \"checkbox\", \"checklist\",\n \"checkmark\", \"checkmark-circle\", \"chef-hat\", \"chevron-down\", \"chevron-left\", \"chevron-right\", \"chevron-up\",\n \"chevrons-right\", \"circle\", \"circle-check\", \"circle-dot\", \"circle-number-1\", \"circle-plus\", \"click\", \"clipboard\",\n \"clipboard-image\", \"clipboard-text\", \"clock\", \"clock-check\", \"clock-hour-4\", \"close\", \"close-circle\", \"code\",\n \"code-block\", \"cog\", \"collapse\", \"color-wheel\", \"columns\", \"columns-2\", \"columns-3\", \"comment\", \"component\",\n \"compose\", \"compose-sparkles\", \"confetti\", \"controls\", \"cookie\", \"copy\", \"credit-card\", \"crop\", \"cube\",\n \"currency-dollar\", \"dashboard\", \"database\", \"database-search\", \"desktop\", \"device-desktop\", \"device-mobile\",\n \"device-tablet\", \"devices\", \"diamond\", \"discount\", \"document\", \"document-pdf\", \"document-remove\", \"document-sheet\",\n \"document-text\", \"document-video\", \"document-word\", \"document-zip\", \"documents\", \"door-exit\", \"dot\",\n \"double-chevron-down\", \"double-chevron-left\", \"double-chevron-right\", \"double-chevron-up\", \"double-quote\",\n \"download\", \"drag-handle\", \"drop\", \"droplet\", \"droplet-half\", \"earth-americas\", \"earth-globe\", \"edit\", \"egg\",\n \"ellipsis-horizontal\", \"ellipsis-vertical\", \"empty\", \"enter\", \"enter-right\", \"envelope\", \"equal\", \"error-filled\",\n \"error-outline\", \"error-screen\", \"expand\", \"eye-closed\", \"eye-off\", \"eye-open\", \"face-happy\", \"face-indifferent\",\n \"face-sad\", \"feather\", \"feedback\", \"file\", \"file-certificate\", \"file-code\", \"file-download\", \"file-settings\", \"file-text\", \"filter\",\n \"flag\", \"flame\", \"flask\", \"focus-2\", \"folder\", \"folders\", \"forms\", \"frame\", \"generate\", \"gift\", \"git-branch\",\n \"github\", \"grid-dots\", \"groq\", \"hammer\", \"hand-finger\", \"hanger\", \"hash\", \"headphones\", \"headset\", \"heart\",\n \"heart-filled\", \"heart-handshake\", \"help-circle\", \"highlight\", \"history\", \"home\", \"hourglass\", \"ice-cream\",\n \"id-badge\", \"id-badge-2\", \"image\", \"image-remove\", \"images\", \"inbox\", \"infinity\", \"info-circle\", \"info-filled\",\n \"info-outline\", \"info-square-rounded\", \"inline\", \"inline-element\", \"input-search\", \"insert-above\", \"insert-below\",\n \"italic\", \"joystick\", \"json\", \"language\", \"launch\", \"layout\", \"layout-align-center\", \"layout-board\",\n \"layout-board-split\", \"layout-bottombar\", \"layout-bottombar-expand\", \"layout-cards\", \"layout-columns\",\n \"layout-dashboard\", \"layout-grid\", \"layout-list\", \"layout-navbar\", \"layout-rows\", \"layout-sidebar\",\n \"layout-sidebar-right\", \"leaf\", \"leave\", \"lemon\", \"library-photo\", \"lifebuoy\", \"link\", \"link-removed\", \"linkedin\",\n \"list\", \"list-check\", \"list-details\", \"list-numbers\", \"list-tree\", \"lock\", \"logo-js\", \"logo-ts\", \"mail\", \"map-2\",\n \"map-pin\", \"map-pins\", \"marker\", \"marker-removed\", \"master-detail\", \"math-function\", \"meat\", \"menu\", \"message\",\n \"message-2\", \"message-check\", \"message-circle\", \"message-circle-2\", \"message-plus\", \"message-star\", \"microphone\",\n \"microphone-slash\", \"mobile-device\", \"mood-empty\", \"moon\", \"movie\", \"news\", \"notes\", \"number\", \"ok-hand\", \"olist\",\n \"overage\", \"package\", \"packages\", \"palette\", \"panel-left\", \"panel-right\", \"paperclip\", \"pause\", \"pencil\", \"phone\",\n \"photo\", \"photo-scan\", \"photo-search\", \"pig-money\", \"pin\", \"pin-filled\", \"pin-removed\", \"play\", \"player-play\",\n \"player-track-next\", \"playlist-add\", \"plug\", \"plug-connected\", \"plus-minus\", \"point\", \"presentation\", \"printer\",\n \"progress-50\", \"progress-75\", \"projects\", \"publish\", \"qrcode\", \"quote\", \"read-only\", \"receipt\", \"recycle\", \"redo\",\n \"refresh\", \"remove\", \"remove-circle\", \"repeat\", \"reset\", \"restore\", \"retrieve\", \"retry\", \"revert\", \"robot\",\n \"rocket\", \"rosette\", \"route\", \"ruler\", \"ruler-measure\", \"salad\", \"scale\", \"schema\", \"school\", \"search\", \"section\",\n \"select\", \"send\", \"server\", \"settings\", \"share\", \"shield-check\", \"shield-lock\", \"shopping-bag\", \"shopping-cart\",\n \"shopping-cart-plus\", \"signature\", \"sitemap\", \"slideshow\", \"sort\", \"sort-descending\", \"spacing-vertical\", \"sparkle\",\n \"sparkles\", \"speakerphone\", \"spinner\", \"split-horizontal\", \"split-vertical\", \"square\", \"stack\", \"stack-2\",\n \"stack-compact\", \"stairs\", \"stairs-up\", \"star\", \"star-filled\", \"stars\", \"stop\", \"strikethrough\", \"string\",\n \"subtask\", \"sun\", \"superscript\", \"sync\", \"table\", \"table-options\", \"tablet-device\", \"tag\", \"tags\", \"target\", \"task\",\n \"template\", \"tent\", \"terminal\", \"terminal-2\", \"text\", \"th-large\", \"th-list\", \"thumb-up\", \"thumbs-down\", \"thumbs-up\",\n \"ticket\", \"tiers\", \"timeline\", \"toggle-arrow-right\", \"toggle-right\", \"token\", \"tool\", \"tools-kitchen-2\", \"transfer\",\n \"translate\", \"trash\", \"trend-upward\", \"triangle-outline\", \"trolley\", \"truck\", \"truck-delivery\", \"truncate\",\n \"twitter\", \"typography\", \"ulist\", \"unarchive\", \"underline\", \"undo\", \"unknown\", \"unlink\", \"unlock\", \"unpublish\",\n \"upload\", \"user\", \"user-circle\", \"user-plus\", \"user-search\", \"user-star\", \"users\", \"users-group\", \"versions\",\n \"video\", \"vocabulary\", \"vs\", \"warning-filled\", \"warning-outline\", \"weight\", \"world\", \"wrench\", \"zoom-question\",\n] as const;\nexport type DockIconName = (typeof DOCK_ICON_NAMES)[number];\nexport const DOCK_ICON_NAME_SET: ReadonlySet<string> = new Set<string>(DOCK_ICON_NAMES);\n\n/* ── Content model fields ────────────────────────────────────────────── */\nexport type ProdFieldType =\n | \"text\" | \"richtext\" | \"document\" | \"image\" | \"boolean\" | \"number\" | \"select\"\n | \"reference\" | \"multi-reference\" | \"multiReference\" | \"array\" | \"date\" | \"datetime\"\n | \"longtext\" | \"slug\" | \"email\" | \"phone\" | \"link\" | \"color\" | \"json\" | \"file\"\n | \"component-ref\" | \"modular\" | \"sections\" | \"location\" | \"button\" | \"multiselect\" | \"icon\" | \"object\"\n | \"tags\" | \"rating\" | \"code\"\n /** V2 video: a URL string or `{ url, poster?, autoplay?, loop?, transcript? }` (VideoValue). */\n | \"video\"\n /** Legacy input only: normalized to `array` + zones on write, still read. */\n | \"group\" | \"repeater\";\n\nexport type OpenMode = \"inline\" | \"drill\" | \"popover\" | \"dialog\";\n/** backend SHOW_IF_OPS (validation/schemas/content.ts). */\nexport type ShowIfOp = \"eq\" | \"neq\" | \"in\" | \"notIn\" | \"gt\" | \"lt\" | \"isSet\" | \"notEmpty\" | \"empty\";\nexport interface ShowIfRule {\n field: string;\n op: ShowIfOp;\n value?: string | string[];\n /** past the siblings (A3, showIfScopeSchema): the parent's level, or the document's top level */\n scope?: \"parent\" | \"root\";\n}\nexport interface ShowIf {\n match: \"all\" | \"any\";\n rules: ShowIfRule[];\n}\n\nexport type UiLayout = \"list\" | \"grid\" | \"table\" | \"rows\" | \"chips\" | \"gallery\";\n/** ui.control (UI_CONTROLS): the leaf controls, the V2 control layouts among them. */\nexport const UI_CONTROLS = [\"segmented\", \"slider\", \"rating\", \"dropdown\", \"radio\", \"cards\", \"grid\", \"switch\", \"checkbox\"] as const;\nexport type UiControl = (typeof UI_CONTROLS)[number];\n/** The V2 control layouts and the types each fits (CONTROL_TYPES); segmented / slider / rating have their own rules. */\nexport const CONTROL_TYPES: Readonly<Partial<Record<UiControl, readonly string[]>>> = {\n dropdown: [\"select\"],\n radio: [\"select\"],\n cards: [\"select\", \"multiselect\"],\n grid: [\"select\"],\n switch: [\"boolean\"],\n checkbox: [\"boolean\", \"multiselect\"],\n};\n/** A row preview: child keys or dotted paths (V2), a literal fallback, and an icon shown while there is no media. */\nexport interface UiPreview {\n title?: string;\n subtitle?: string;\n media?: string;\n fallback?: string;\n icon?: DockIconName;\n}\nexport interface FieldUi {\n collapsed?: boolean;\n preview?: UiPreview;\n layout?: UiLayout;\n reorderable?: boolean;\n control?: UiControl;\n icon?: DockIconName;\n itemLabel?: string;\n /** text / longtext: the lines a text box opens with (1–40). */\n rows?: number;\n /** a field's placeholder when its config is strict (a prop keeps its own `placeholder`). */\n placeholder?: string;\n /** a list's item type: its identifier and its title (V2 `of[0].name` / `title`). */\n itemName?: string;\n itemTitle?: string;\n /** the value comes from a global: an edit applies everywhere. */\n synced?: { from: string };\n /** link / image / button: a plain string or the object. */\n valueShape?: \"string\" | \"object\";\n /** V2 field access (D58): the editor gates, the backend stores. */\n access?: { view?: \"schema.view\"; edit?: \"schema.manage\" };\n /** true = never shown in the form; false ≡ absent. */\n hidden?: boolean;\n /** modular: per block slug, how a row of that block type reads (V2 `of[i].icon` / `preview`). */\n blocks?: Record<string, { icon?: DockIconName; preview?: UiPreview }>;\n input?: { name: string; writes?: string[] };\n}\n\nexport type ScaleUnit = \"px\" | \"%\" | \"rem\" | \"em\" | \"vw\" | \"vh\";\nexport interface ScaleCustomValue {\n value: number;\n unit: ScaleUnit;\n}\nexport interface ScaleTokenStep {\n value: string;\n label: string;\n title?: string;\n px?: number;\n css?: string;\n}\nexport type ScaleBound = number | Partial<Record<ScaleUnit, number>>;\n/**\n * A number's `config.scale` (CPO D41): a stepped range, numeric steps or token steps, units, presets, and the custom\n * escape hatch (a boolean, or its bounds). A token scale stores the token string; a custom value `{ value, unit }`.\n */\nexport interface NumberScale {\n steps: { min: number; max: number; step?: number } | Array<{ value: number; label?: string }> | ScaleTokenStep[];\n units?: ScaleUnit[];\n defaultUnit?: ScaleUnit;\n allowCustom?: boolean | { min?: ScaleBound; max?: ScaleBound; units?: ScaleUnit[]; step?: number };\n presets?: Array<{ label?: string; value: number | string | ScaleCustomValue }>;\n}\n\nexport interface ArrayZoneConfig {\n nonRepeatable?: ContentModelField[];\n repeatable?: { fields: ContentModelField[]; minItems?: number; maxItems?: number };\n}\n\n/** One choice: a bare value, or `{ label, value }` with optional chrome (a dock icon, one line ≤ 140 under the label). */\nexport interface ChoiceOption {\n label: string;\n value: string;\n icon?: DockIconName;\n description?: string;\n}\nexport type SelectOption = string | ChoiceOption;\n\n/**\n * A list of scalars' item settings (backend arrayItemSettingsSchema, an `array` prop's `config.item`): the V2 item field's\n * description, locale chip, required, and a text / rich text item's length (a text item's pattern).\n */\nexport interface ArrayItemSettings {\n helpText?: string;\n localized?: boolean;\n required?: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n patternMessage?: string;\n}\n\n/** The typed keys of a field's `config` bag; the bag itself stays loose on scalar types. */\nexport interface FieldConfig {\n /** primitive array: scalars, or images, links or rich text (each item judged by its own type). */\n itemType?: \"text\" | \"number\" | \"date\" | \"image\" | \"link\" | \"richtext\";\n zones?: ArrayZoneConfig;\n contentModelId?: string;\n polymorphic?: boolean;\n allowedModelSlugs?: string[];\n /** reference / multi-reference (props): the non-entry targets, and a filter over the target's fields. */\n targets?: (\"page\" | \"form\" | \"collection\")[];\n filter?: string;\n min?: number;\n max?: number;\n step?: number;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n /** text / longtext: the message shown when the pattern does not match (≤ 200, needs `pattern`). */\n patternMessage?: string;\n /** number: the error bounds (V2 validation.min / max); they win over min / max. */\n validation?: { min?: number; max?: number };\n /** number: what it counts (\"%\", \"USD\"), 1–24 characters. */\n unit?: string;\n placeholder?: string;\n includeTime?: boolean;\n /** \"site\", an IANA zone name, true (the editor picks a zone) or false (none). */\n timezone?: string | boolean;\n blockSlugs?: string[];\n minItems?: number;\n maxItems?: number;\n allowedSections?: string[];\n componentIds?: string[];\n /** slot (props): the V2 `of.allow` block names it takes, kept as written (backend slotAllowIssues). */\n allow?: string[];\n /** array (props): the item's own settings (backend arrayItemSettingsSchema). */\n item?: ArrayItemSettings;\n scale?: NumberScale;\n /** multiselect: its choices; with `allowCustom` it is free tags. tags: its suggestions. */\n options?: SelectOption[];\n allowCustom?: boolean;\n /** code: the language, and the languages an editor may pick (strict config, with tags' and rating's). */\n language?: string;\n languages?: string[];\n /**\n * text: \"url\" marks a text field the kit edits as a URL (V2 `url`). The backend keeps a text field's config as sent and\n * enforces its minLength / maxLength / pattern, which a link field would not; it reads no other meaning into the key.\n */\n inputType?: \"url\";\n [key: string]: unknown;\n}\n\nexport interface ContentModelField {\n key: string;\n label: string;\n type: ProdFieldType;\n helpText?: string;\n required?: boolean;\n unique?: boolean;\n defaultValue?: unknown;\n /** select: strings or { label, value }. */\n options?: SelectOption[];\n config?: FieldConfig | null;\n /** object (and legacy group / repeater): child fields. */\n fields?: ContentModelField[];\n openMode?: OpenMode;\n helpMore?: string;\n advanced?: boolean;\n /** the AI-assist sparkle (A3: baseField and a prop's `ai`) */\n ai?: boolean;\n recommendedMax?: number;\n fieldsetId?: string;\n placement?: \"cover\" | \"title\" | \"excerpt\" | \"body\" | \"inline\" | \"panel\";\n showIf?: ShowIf;\n readOnly?: true | \"developer\";\n lockReason?: string;\n /** V2 per-field localization: once any field is marked, a translation owns only its localized values. */\n localized?: boolean;\n ui?: FieldUi;\n searchable?: boolean;\n [key: string]: unknown;\n}\n\nexport type FieldsetKind = \"group\" | \"row\" | \"tab\";\nexport interface Fieldset {\n id: string;\n name: string;\n description?: string;\n kind?: FieldsetKind;\n icon?: DockIconName;\n collapsed?: boolean;\n tab?: string;\n collapsible?: boolean;\n columns?: 1 | 2;\n layout?: \"stack\" | \"breakpoints\";\n}\n\n/** A content model as the adapters read and write it (server-owned ids and timestamps left out). */\nexport interface ProdContentModel {\n name: string;\n slug: string;\n description?: string;\n kind?: \"model\" | \"block\";\n /** the collection's dock icon (content_models.icon, migration 0458; modelIconSchema) */\n icon?: DockIconName;\n fields: ContentModelField[];\n fieldsets?: Fieldset[];\n /** how an entry row reads in the list (content_models.list_preview, widened to V2 PreviewConfig by A3) */\n listPreview?: ComponentPreview | null;\n /** content_models.v2 (A3, contentModelV2Schema): what V2 says that has no native column */\n v2?: { shape?: V2Shape; placement?: V2Placement; category?: KitCategory; variants?: Record<string, { icon?: DockIconName; description?: string }> };\n}\n\n/* ── Component props ─────────────────────────────────────────────────── */\nexport const COMPONENT_PROP_TYPES = [\n \"text\", \"richtext\", \"image\", \"url\", \"boolean\", \"number\", \"select\", \"group\", \"table\", \"slot\", \"form\",\n \"link\", \"button\", \"date\", \"color\", \"file\", \"icon\", \"multiselect\",\n \"object\", \"datetime\", \"slug\", \"email\", \"phone\", \"json\", \"location\", \"tags\", \"rating\", \"code\",\n /** an entry id (or `{ entryId, modelSlug }`) and a list of them, hydrated on delivery; no definition default. */\n \"reference\", \"multi-reference\",\n /** a video value, a list of `config.itemType` values, multi-line text. */\n \"video\", \"array\", \"longtext\",\n] as const;\nexport type ComponentPropType = (typeof COMPONENT_PROP_TYPES)[number];\nexport type ComponentSubFieldType = Exclude<ComponentPropType, \"slot\" | \"form\">;\n\nexport const COMPONENT_CATEGORIES = [\n \"navbar\", \"footer\", \"button\", \"section\", \"slider\", \"tabs\", \"form\", \"custom\", \"hero\", \"content\", \"social-proof\", \"conversion\",\n] as const;\nexport type ComponentCategory = (typeof COMPONENT_CATEGORIES)[number];\n\nexport interface PropGroup {\n id: string;\n label?: string;\n kind?: \"group\" | \"row\";\n icon?: DockIconName;\n collapsed?: boolean;\n /** V2 fieldset keys, on the defining prop: a line under the header (≤ 200), side by side, a static header, a\n * breakpoint switcher, and its place among the groups (0–99; absent = where its first prop sits). */\n description?: string;\n columns?: 1 | 2;\n collapsible?: boolean;\n layout?: \"stack\" | \"breakpoints\";\n order?: number;\n}\n/**\n * A top-level prop's tab. Defined once: the first top-level prop with an id carries its label / icon / order, later\n * props carry `{ id }` only. A nested prop names one as `{ id }` only (tab_nested refuses a definition there; V2 and\n * the dock ignore it); one ui.group's members share one tab.\n */\nexport interface PropTab {\n id: string;\n label?: string;\n icon?: DockIconName;\n /** its place among the tabs (0–99; absent = where its first prop sits). */\n order?: number;\n}\nexport type PropUi = FieldUi & { group?: PropGroup; tab?: PropTab };\n\nexport interface ComponentSubField {\n key: string;\n label: string;\n type: ComponentSubFieldType;\n defaultValue?: unknown;\n required?: boolean;\n /** null = no help on purpose (V2 has no description); absent = the platform's auto-help. */\n helpText?: string | null;\n placeholder?: string;\n /** strings or { label, value } (subFieldOptionSchema). */\n options?: SelectOption[];\n helpMore?: string;\n advanced?: boolean;\n /** the AI-assist sparkle (A3: baseField and a prop's `ai`) */\n ai?: boolean;\n recommendedMax?: number;\n /** on a table's sub-field: no two rows share it; on a list: no item repeats. */\n unique?: boolean;\n openMode?: OpenMode;\n showIf?: ShowIf;\n /** the prop's ui, inline group included (V2 fieldsets nest). */\n ui?: PropUi;\n readOnly?: true | \"developer\";\n lockReason?: string;\n localized?: boolean;\n fields?: ComponentSubField[];\n config?: { fields?: ComponentSubField[]; options?: SelectOption[] } & Record<string, unknown>;\n}\n\nexport interface ComponentPropDef {\n key: string;\n label: string;\n /**\n * Where the override writes inside the component's blockJson. Binding, not schema: the V2 language has no twin, so\n * fromTypeSchema leaves it unset and the host binds it (componentize lane) before the save.\n */\n target?: { blockId: string; path: string };\n type: ComponentPropType;\n defaultValue?: unknown;\n required?: boolean;\n /** null = no help on purpose (V2 has no description); absent = the platform's auto-help. */\n helpText?: string | null;\n placeholder?: string;\n showIf?: ShowIf;\n helpMore?: string;\n advanced?: boolean;\n /** the AI-assist sparkle (A3: baseField and a prop's `ai`) */\n ai?: boolean;\n recommendedMax?: number;\n /** a list prop's items (a table's rows) distinct; refused on a top-level scalar (unique_prop_scope). */\n unique?: boolean;\n openMode?: OpenMode;\n readOnly?: true | \"developer\";\n lockReason?: string;\n localized?: boolean;\n config?: {\n componentIds?: string[];\n /** slot: the V2 block names it takes (`textBlock`), resolved to the project's components by the dock. */\n allow?: string[];\n /** array: the item's own settings. */\n item?: ArrayItemSettings;\n multiple?: boolean;\n minItems?: number;\n maxItems?: number;\n options?: SelectOption[];\n fields?: ComponentSubField[];\n min?: number;\n max?: number;\n step?: number;\n /** number: the error bounds (they win over min / max). */\n validation?: { min?: number; max?: number };\n /** text / url / longtext / richtext: length (richtext: its plain text). text / url / longtext: a pattern and its message. */\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n patternMessage?: string;\n /** reference / multi-reference: the target model, or polymorphic with an allowlist of model slugs. */\n contentModelId?: string;\n polymorphic?: boolean;\n allowedModelSlugs?: string[];\n /** reference / multi-reference: page / form / collection targets (backend REF_TARGET_KINDS) and a filter over the target's fields. */\n targets?: (\"page\" | \"form\" | \"collection\")[];\n filter?: string;\n /** array: what the list holds. */\n itemType?: \"text\" | \"number\" | \"date\" | \"image\" | \"link\" | \"richtext\";\n /** table with mode \"types\" (a polymorphic list): its item types, each row's `_type` naming one. */\n itemTypes?: { name: string; label?: string; fields: ComponentSubField[]; icon?: DockIconName; preview?: UiPreview }[];\n } & Record<string, unknown>;\n ui?: PropUi;\n}\n\n/** A component as the adapters read and write it. */\n/**\n * How ONE instance of a component reads as a row in a parent's slot list (backend `components.preview`, migration 0459;\n * types ComponentPreview): V2's document-level `preview`. `title` / `subtitle` / `media` each name a TOP-LEVEL prop key\n * (the API refuses any other: preview_key_unknown), `fallback` is the literal row label (trimmed, 1..120), `icon` a dock\n * icon shown while the row has no media.\n */\nexport type ComponentPreview = UiPreview;\n/** backend componentPreviewSchema: a preview slot names a key of at most this many characters */\nexport const COMPONENT_PREVIEW_KEY_MAX = 64;\n/** backend componentPreviewSchema: the row's literal fallback label, trimmed, at most this many characters */\nexport const COMPONENT_PREVIEW_FALLBACK_MAX = 120;\n\nexport interface ProdComponent {\n name: string;\n slug: string;\n description?: string;\n /** a library id, or since A3 the kit catalogue's own category verbatim (CATEGORY_ORDER) */\n category?: ComponentCategory | KitCategory;\n props: ComponentPropDef[];\n preview?: ComponentPreview | null;\n /** The component's dock icon (backend `components.icon`, dockIconSchema): V2's block icon. */\n icon?: DockIconName | null;\n /** The V2 tabs no top-level prop sits in (backend `components.tabs`, componentTabsSchema): declared, shown by none. */\n tabs?: PropTab[] | null;\n /** components.v2 (A3, componentV2Schema): category and icon are native columns */\n v2?: { shape?: V2Shape; placement?: V2Placement };\n /** the section family it is a variant of (its label): every member of a V2 block's variant family names the block */\n sectionType?: string;\n}\n\n/* ── Stored values ───────────────────────────────────────────────────── */\nexport const LINK_RELS = [\"nofollow\", \"sponsored\", \"ugc\"] as const;\nexport type LinkRel = (typeof LINK_RELS)[number];\nexport const LINK_VALUE_TYPES = [\"url\", \"page\", \"entry\", \"email\", \"phone\", \"anchor\", \"section\", \"file\"] as const;\nexport type LinkValueType = (typeof LINK_VALUE_TYPES)[number];\n/** The F1 link object (link-value.ts). A bare href string stays valid too. */\nexport interface ProdLinkValue {\n type?: LinkValueType;\n href: string;\n title?: string;\n newTab?: boolean;\n target?: \"_self\" | \"_blank\";\n rel?: LinkRel;\n section?: string;\n /** page / entry links: the target's id. */\n ref?: string;\n}\n\nexport const BUTTON_VARIANTS = [\"primary\", \"secondary\", \"tertiary\", \"brand\", \"dark\", \"light\", \"outline\", \"link\"] as const;\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number];\nexport const BUTTON_SIZES = [\"sm\", \"md\", \"lg\"] as const;\n/** One button as one value (button-look.ts ButtonValue). */\nexport interface ProdButtonValue {\n label: string;\n href: string | ProdLinkValue;\n variant?: ButtonVariant;\n size?: (typeof BUTTON_SIZES)[number];\n arrow?: boolean;\n icon?: DockIconName | null;\n newTab?: boolean;\n rel?: LinkRel;\n /** Stretch across the column (backend button_full_width_invalid refuses a non-boolean a write brings). */\n fullWidth?: boolean;\n}\n\n/** 0–1 fractions of the image cut from each edge. */\nexport interface ProdImageCrop {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n/** The normalized image value (field-value-normalize.ts): other keys are dropped on write. */\nexport interface ProdImageValue {\n id?: string;\n url: string;\n name?: string;\n altText?: string;\n /** 0–1 fractions. */\n focal?: { x: number; y: number };\n crop?: ProdImageCrop;\n}\n\n/** The V2 video value's object form (video-value.ts): exactly the video block's props. A bare URL string is valid too. */\nexport const VIDEO_VALUE_KEYS = [\"url\", \"poster\", \"autoplay\", \"loop\", \"transcript\"] as const;\nexport interface ProdVideoValue {\n url: string;\n poster?: string;\n autoplay?: boolean;\n loop?: boolean;\n transcript?: string;\n}\n\n/** A polymorphic reference value. */\nexport interface ProdPolymorphicRef {\n entryId: string;\n modelSlug: string;\n}\n/**\n * A page, form or collection pick on a prop reference that also takes entries (backend reference-target-config.ts\n * targetRefOf): a bare string there is read as an entry id (acceptsEntries), so a non-entry pick names its kind.\n */\nexport interface ProdTargetRef {\n kind: \"page\" | \"form\" | \"collection\";\n id: string;\n}\n/** What a `form` prop stores. */\nexport interface ProdFormRef {\n formId: string;\n}\n"],"mappings":"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"}
@@ -152,3 +152,42 @@ export declare function fromTypeSchema(v2: TypeSchema, target: "props"): FromTyp
152
152
  export declare function toTypeSchema(model: ProdContentModel, opts?: ToTypeSchemaOptions): TypeSchema;
153
153
  /** A prod component's props → TypeSchema (kind "block"); prop groups read back as fieldsets. */
154
154
  export declare function propsToTypeSchema(component: ProdComponent, opts?: ToTypeSchemaOptions): TypeSchema;
155
+ /**
156
+ * V2's block variants as prod stores them for components (a kit block made a section): one component per variant, all
157
+ * one family (`sectionType` = the block's title, name = the variant's), and a variant group (component_variant_groups)
158
+ * whose `v2.variants` lists the members in order with their description. Pure: the host creates the members, then the
159
+ * group with the ids it got back (`variantGroupV2`). A block without variants, or with an id no slug can carry, is the
160
+ * one component `fromTypeSchema(v2, "props")` makes, its variants named as catalog.
161
+ */
162
+ export interface VariantFamilyPlan {
163
+ members: ProdComponent[];
164
+ /** null: no family (one component) */
165
+ group: {
166
+ name: string;
167
+ slug: string;
168
+ variants: {
169
+ member: string;
170
+ description?: string;
171
+ }[];
172
+ } | null;
173
+ lossy: LossyItem[];
174
+ catalog: CatalogItem[];
175
+ }
176
+ export declare function variantFamilyFromTypeSchema(v2: TypeSchema): VariantFamilyPlan;
177
+ /** The group's `v2` (component_variant_groups.v2, variantGroupV2Schema) once the members exist: `idOf` maps a member slug to its id. */
178
+ export declare function variantGroupV2(group: NonNullable<VariantFamilyPlan["group"]>, idOf: (memberSlug: string) => string): {
179
+ variants: {
180
+ description?: string | undefined;
181
+ componentId: string;
182
+ }[];
183
+ };
184
+ /**
185
+ * A family read back as one V2 block: the first member's props, the family's name and title, and its variants from the
186
+ * group's members in `v2.variants` order (the id after the group slug, the member's name, the meta's description).
187
+ */
188
+ export declare function variantFamilyToTypeSchema(members: ProdComponent[], group: {
189
+ slug: string;
190
+ variants: {
191
+ description?: string;
192
+ }[];
193
+ }, opts?: ToTypeSchemaOptions): TypeSchema;
@@ -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). */
@@ -613,7 +651,7 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
613
651
  case "hidden":
614
652
  if (typeof f.hidden === "boolean") node.ui.hidden = f.hidden;
615
653
  else {
616
- const showIf = conditionToShowIf(f.hidden, p, siblings, ctx, props && top);
654
+ const showIf = conditionToShowIf(f.hidden, p, siblings, ctx, top && (props || !!ctx.variants));
617
655
  if (showIf) node.showIf = showIf;
618
656
  }
619
657
  break;
@@ -646,7 +684,7 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
646
684
  node.advanced = f.advanced;
647
685
  break;
648
686
  case "ai":
649
- cat(ctx, p, "ai");
687
+ node.ai = f.ai;
650
688
  break;
651
689
  case "reference":
652
690
  if (shape.kind === "leaf" && shape.type === "reference") referenceToConfig(f.reference, node, p, ctx);
@@ -1277,8 +1315,8 @@ function layoutToProd(f, shape, node, at, ctx) {
1277
1315
  const t = shape.type;
1278
1316
  const n = f.options?.length ?? 0;
1279
1317
  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");
1318
+ if (n >= SEGMENTED_MIN && n <= SEGMENTED_MAX) node.ui.control = "segmented";
1319
+ else if (n > SEGMENTED_MAX) cat(ctx, at, "segmented fallback");
1282
1320
  else lose(ctx, at, "layout");
1283
1321
  } else if (l === "slider" && t === "number" && f.type === "number") node.ui.control = "slider";
1284
1322
  else if (UI_CONTROLS.includes(l) && l !== "segmented" && l !== "slider" && CONTROL_TYPES[l]?.includes(t)) node.ui.control = l;
@@ -1784,13 +1822,14 @@ function conditionToShowIf(h, at, siblings, ctx, variantOk) {
1784
1822
  /** 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
1823
  function conditionRule(r, head, siblings, variantOk) {
1786
1824
  const variant = variantOk && r.field === "_variant";
1787
- if (!r.field || r.scope !== void 0 || !variant && !siblings.some((s) => s.name === r.field)) return null;
1825
+ if (!r.field || r.scope === void 0 && !variant && !siblings.some((s) => s.name === r.field)) return null;
1788
1826
  const allowed = head ? [
1789
1827
  "field",
1790
1828
  "equals",
1791
1829
  "notEmpty",
1792
1830
  "op",
1793
1831
  "value",
1832
+ "scope",
1794
1833
  "also",
1795
1834
  "match"
1796
1835
  ] : [
@@ -1798,7 +1837,8 @@ function conditionRule(r, head, siblings, variantOk) {
1798
1837
  "equals",
1799
1838
  "notEmpty",
1800
1839
  "op",
1801
- "value"
1840
+ "value",
1841
+ "scope"
1802
1842
  ];
1803
1843
  if (Object.keys(r).some((k) => !allowed.includes(k) && r[k] !== void 0)) return null;
1804
1844
  if ([
@@ -1806,22 +1846,26 @@ function conditionRule(r, head, siblings, variantOk) {
1806
1846
  r.equals !== void 0,
1807
1847
  r.notEmpty !== void 0
1808
1848
  ].filter(Boolean).length !== 1) return null;
1849
+ const scope = r.scope ? { scope: r.scope } : {};
1809
1850
  if (r.notEmpty !== void 0) return r.notEmpty === true ? {
1810
1851
  field: r.field,
1811
- op: "notEmpty"
1852
+ op: "notEmpty",
1853
+ ...scope
1812
1854
  } : null;
1813
1855
  const target = r.op !== void 0 ? r.value : r.equals;
1814
1856
  const op = r.op !== void 0 ? SHOW_IF_OP[r.op] : Array.isArray(target) ? "in" : "eq";
1815
1857
  if (!op) return null;
1816
1858
  if (VALUELESS.has(op)) return r.value === void 0 ? {
1817
1859
  field: r.field,
1818
- op
1860
+ op,
1861
+ ...scope
1819
1862
  } : null;
1820
- const sibType = variant ? void 0 : siblings.find((s) => s.name === r.field)?.type;
1863
+ const sibType = variant || r.scope ? void 0 : siblings.find((s) => s.name === r.field)?.type;
1821
1864
  if (op === "gt" || op === "lt") return typeof target === "number" && Number.isFinite(target) ? {
1822
1865
  field: r.field,
1823
1866
  op,
1824
- value: String(target)
1867
+ value: String(target),
1868
+ ...scope
1825
1869
  } : null;
1826
1870
  const list = Array.isArray(target) ? target : [target];
1827
1871
  const strs = [];
@@ -1838,16 +1882,19 @@ function conditionRule(r, head, siblings, variantOk) {
1838
1882
  if (op === "in" || op === "notIn") return {
1839
1883
  field: r.field,
1840
1884
  op,
1841
- value: strs
1885
+ value: strs,
1886
+ ...scope
1842
1887
  };
1843
1888
  return Array.isArray(target) ? strs.length ? {
1844
1889
  field: r.field,
1845
1890
  op: op === "eq" ? "in" : "notIn",
1846
- value: strs
1891
+ value: strs,
1892
+ ...scope
1847
1893
  } : null : {
1848
1894
  field: r.field,
1849
1895
  op,
1850
- value: strs[0]
1896
+ value: strs[0],
1897
+ ...scope
1851
1898
  };
1852
1899
  }
1853
1900
  var nonEmpty = (o) => Object.keys(o).length ? o : void 0;
@@ -1864,6 +1911,7 @@ function common(n) {
1864
1911
  "showIf",
1865
1912
  "helpMore",
1866
1913
  "advanced",
1914
+ "ai",
1867
1915
  "recommendedMax",
1868
1916
  "openMode",
1869
1917
  "localized",
@@ -1957,9 +2005,12 @@ function fromTypeSchema(v2, target) {
1957
2005
  blockModels: /* @__PURE__ */ new Map()
1958
2006
  };
1959
2007
  const props = target === "props";
2008
+ if (!props && v2.variants?.length) ctx.variants = true;
1960
2009
  const slug = kebab(v2.name);
1961
- const category = v2.category !== void 0 ? CATEGORY_TO_PROD[v2.category] : void 0;
2010
+ const kitCategory = KIT_CATEGORIES.includes(v2.category ?? "") ? v2.category : void 0;
2011
+ const category = kitCategory ?? (v2.category !== void 0 ? CATEGORY_TO_PROD[v2.category] : void 0);
1962
2012
  const fieldsets = [];
2013
+ const block = {};
1963
2014
  for (const k of Object.keys(v2)) {
1964
2015
  if (v2[k] === void 0) continue;
1965
2016
  const p = `/${k}`;
@@ -1973,23 +2024,22 @@ function fromTypeSchema(v2, target) {
1973
2024
  case "fieldsets":
1974
2025
  case "groups": break;
1975
2026
  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");
2027
+ if (props ? v2.kind !== "block" : v2.kind !== "entry") block.shape = v2.kind;
1978
2028
  break;
1979
2029
  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");
2030
+ if (!dockIconOf(v2.icon)) cat(ctx, p, "block icon");
1984
2031
  break;
2032
+ case "preview": break;
1985
2033
  case "variants":
1986
- cat(ctx, p, "variants");
2034
+ if (!ctx.variants) cat(ctx, p, "variants");
1987
2035
  break;
1988
2036
  case "category":
1989
- if (!props || !category || capitalize(category) !== v2.category) cat(ctx, p, "category");
2037
+ if (kitCategory) {
2038
+ if (!props) block.category = kitCategory;
2039
+ } else if (!props || !category || capitalize(category) !== v2.category) cat(ctx, p, "category");
1990
2040
  break;
1991
2041
  case "placement":
1992
- cat(ctx, p, "block placement");
2042
+ block.placement = v2.placement;
1993
2043
  break;
1994
2044
  case "initialValue":
1995
2045
  cat(ctx, p, "seeded instance");
@@ -2010,6 +2060,7 @@ function fromTypeSchema(v2, target) {
2010
2060
  };
2011
2061
  if (v2.description !== void 0) component.description = v2.description;
2012
2062
  if (category) component.category = category;
2063
+ if (nonEmpty(block)) component.v2 = { ...block };
2013
2064
  const icon = v2.icon === void 0 ? void 0 : dockIconOf(v2.icon);
2014
2065
  if (icon) component.icon = icon;
2015
2066
  if (v2.preview !== void 0) {
@@ -2030,7 +2081,30 @@ function fromTypeSchema(v2, target) {
2030
2081
  fields: nodes.map(emitField)
2031
2082
  };
2032
2083
  if (v2.description !== void 0) model.description = v2.description;
2033
- model.kind = "model";
2084
+ const modelIcon = v2.icon === void 0 ? void 0 : dockIconOf(v2.icon);
2085
+ if (modelIcon) model.icon = modelIcon;
2086
+ model.kind = block.shape === "block" && model.fields.every(blockRenders) ? "block" : "model";
2087
+ if (ctx.variants) {
2088
+ const variants = v2.variants;
2089
+ model.fields.unshift({
2090
+ key: BLOCK_VARIANT_KEY,
2091
+ label: "Variant",
2092
+ type: "select",
2093
+ required: false,
2094
+ options: variants.map((v) => ({
2095
+ label: v.name,
2096
+ value: v.id
2097
+ }))
2098
+ });
2099
+ model.v2 = {
2100
+ ...block,
2101
+ variants: Object.fromEntries(variants.map((v) => [v.id, v.description !== void 0 ? { description: v.description } : {}]))
2102
+ };
2103
+ } else if (nonEmpty(block)) model.v2 = { ...block };
2104
+ if (v2.preview !== void 0) {
2105
+ const preview = componentPreviewToProd(v2.preview, new Set(model.fields.map((x) => x.key)), v2.fields, "/preview", ctx);
2106
+ if (Object.keys(preview).length) model.listPreview = preview;
2107
+ }
2034
2108
  if (fieldsets.length) model.fieldsets = fieldsets;
2035
2109
  return {
2036
2110
  schema: model,
@@ -2414,9 +2488,12 @@ function previewFromProd(pv, children) {
2414
2488
  function showIfToCondition(s, siblings, at, ctx) {
2415
2489
  const conds = [];
2416
2490
  for (const r of s.rules) {
2417
- const c = showIfRuleToCondition(r, siblings);
2491
+ const c = showIfRuleToCondition(r, r.scope ? [] : siblings);
2418
2492
  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);
2493
+ conds.push(r.scope ? {
2494
+ ...c,
2495
+ scope: r.scope
2496
+ } : c);
2420
2497
  }
2421
2498
  if (!conds.length) return void 0;
2422
2499
  const [head, ...also] = conds;
@@ -2530,6 +2607,7 @@ function readToV2(n, at, siblings, ctx, top, lane) {
2530
2607
  if (n.unique !== void 0) val.unique = n.unique;
2531
2608
  if (n.recommendedMax !== void 0) val.recommendedMax = n.recommendedMax;
2532
2609
  if (n.advanced !== void 0) f.advanced = n.advanced;
2610
+ if (n.ai !== void 0) f.ai = n.ai;
2533
2611
  if (n.openMode !== void 0) f.openMode = n.openMode;
2534
2612
  if (n.readOnly === true) f.readOnly = true;
2535
2613
  if (n.archived === true) f.archived = true;
@@ -2972,14 +3050,27 @@ function toTypeSchema(model, opts = {}) {
2972
3050
  if (s.icon !== void 0) ctx.onLoss(`/fieldsets/${i}/icon`, "fieldset icon", "V2 sections have no icon.");
2973
3051
  fieldsets.push(fs);
2974
3052
  });
2975
- const nodes = model.fields.map(readField);
3053
+ const variantField = model.v2?.variants ? model.fields.find((f) => f.key === BLOCK_VARIANT_KEY && f.type === "select") : void 0;
3054
+ const nodes = model.fields.filter((f) => f !== variantField).map(readField);
2976
3055
  const out = {
2977
3056
  name: camel(model.slug),
2978
3057
  title: model.name,
2979
- kind: model.kind === "block" ? "block" : "entry",
3058
+ kind: model.kind === "block" ? "block" : model.v2?.shape ?? "entry",
2980
3059
  fields: nodes.map((n, i) => readToV2(n, `/fields/${i}`, nodes, ctx, true, "fields"))
2981
3060
  };
3061
+ if (model.v2?.placement) out.placement = model.v2.placement;
3062
+ if (variantField) {
3063
+ const meta = model.v2.variants;
3064
+ out.variants = (variantField.options ?? []).map((o) => ({
3065
+ id: o.value,
3066
+ name: o.label,
3067
+ ...meta[o.value]?.description !== void 0 ? { description: meta[o.value].description } : {}
3068
+ }));
3069
+ }
3070
+ if (model.listPreview) out.preview = previewFromProd(model.listPreview, nodes);
3071
+ if (model.v2?.category) out.category = model.v2.category;
2982
3072
  if (model.description !== void 0) out.description = model.description;
3073
+ if (model.icon) out.icon = tablerIconOf(model.icon);
2983
3074
  if (fieldsets.length) out.fieldsets = fieldsets;
2984
3075
  if (groups.length) out.groups = groups;
2985
3076
  return out;
@@ -3048,18 +3139,72 @@ function propsToTypeSchema(component, opts = {}) {
3048
3139
  const out = {
3049
3140
  name: camel(component.slug),
3050
3141
  title: component.name,
3051
- kind: "block",
3142
+ kind: component.v2?.shape ?? "block",
3052
3143
  fields: nodes.map((n, i) => readToV2(n, `/fields/${i}`, nodes, ctx, true, "props"))
3053
3144
  };
3145
+ if (component.v2?.placement) out.placement = component.v2.placement;
3054
3146
  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);
3147
+ if (component.category !== void 0 && component.category !== "custom") out.category = KIT_CATEGORIES.includes(component.category) ? component.category : capitalize(component.category);
3056
3148
  if (component.icon) out.icon = tablerIconOf(component.icon);
3057
3149
  if (component.preview) out.preview = previewFromProd(component.preview, nodes);
3058
3150
  if (fieldsets.length) out.fieldsets = fieldsets;
3059
3151
  if (groups.length) out.groups = groups;
3060
3152
  return out;
3061
3153
  }
3154
+ /** A V2 variant id a member slug can carry, so the read takes it back verbatim (the component slug rule). */
3155
+ var VARIANT_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3156
+ function variantFamilyFromTypeSchema(v2) {
3157
+ const base = fromTypeSchema(v2, "props");
3158
+ const variants = v2.variants ?? [];
3159
+ if (!variants.length || !variants.every((v) => VARIANT_ID_RE.test(v.id))) return {
3160
+ members: [base.schema],
3161
+ group: null,
3162
+ lossy: base.lossy,
3163
+ catalog: base.catalog
3164
+ };
3165
+ const slug = base.schema.slug;
3166
+ return {
3167
+ members: variants.map((v) => ({
3168
+ ...base.schema,
3169
+ name: v.name,
3170
+ slug: `${slug}-${v.id}`,
3171
+ sectionType: v2.title
3172
+ })),
3173
+ group: {
3174
+ name: v2.title,
3175
+ slug,
3176
+ variants: variants.map((v) => ({
3177
+ member: `${slug}-${v.id}`,
3178
+ ...v.description !== void 0 ? { description: v.description } : {}
3179
+ }))
3180
+ },
3181
+ lossy: base.lossy,
3182
+ catalog: base.catalog.filter((c) => c.feature !== "variants")
3183
+ };
3184
+ }
3185
+ /** The group's `v2` (component_variant_groups.v2, variantGroupV2Schema) once the members exist: `idOf` maps a member slug to its id. */
3186
+ function variantGroupV2(group, idOf) {
3187
+ return { variants: group.variants.map((v) => ({
3188
+ componentId: idOf(v.member),
3189
+ ...v.description !== void 0 ? { description: v.description } : {}
3190
+ })) };
3191
+ }
3192
+ /**
3193
+ * A family read back as one V2 block: the first member's props, the family's name and title, and its variants from the
3194
+ * group's members in `v2.variants` order (the id after the group slug, the member's name, the meta's description).
3195
+ */
3196
+ function variantFamilyToTypeSchema(members, group, opts = {}) {
3197
+ const out = propsToTypeSchema(members[0], opts);
3198
+ out.name = camel(group.slug);
3199
+ if (members[0].sectionType) out.title = members[0].sectionType;
3200
+ out.variants = members.map((m, i) => ({
3201
+ id: m.slug.slice(group.slug.length + 1),
3202
+ name: m.name,
3203
+ ...group.variants[i]?.description !== void 0 ? { description: group.variants[i].description } : {}
3204
+ }));
3205
+ return out;
3206
+ }
3062
3207
  //#endregion
3063
- export { CATEGORY_TO_PROD, SHOPIFY_REF_LOCK, fromTypeSchema, leafValuePlan, planFromProd, planToProd, propsToTypeSchema, schemaSlugOf, toTypeSchema };
3208
+ export { CATEGORY_TO_PROD, SHOPIFY_REF_LOCK, fromTypeSchema, leafValuePlan, planFromProd, planToProd, propsToTypeSchema, schemaSlugOf, toTypeSchema, variantFamilyFromTypeSchema, variantFamilyToTypeSchema, variantGroupV2 };
3064
3209
 
3065
3210
  //# sourceMappingURL=schema.js.map